Setup

library(data.table)
library(DBI)
library(ggplot2)
library(cowplot)
library(grid)

Sys.setlocale("LC_TIME", "en_US.UTF-8") # Print English date format
[1] "en_US.UTF-8"
en_US.UTF-8
# Sys.setlocale("LC_TIME", "nl_NL.UTF-8") # Print Dutch date format

number_format <- scales::number_format(big.mark = ",", decimal.mark = ".") # Print English number format
# number_format <- scales::number_format(big.mark = ".", decimal.mark = ",") # Print Dutch number format

theme_paper <- theme_classic(base_size = 12) + 
  theme(axis.text = element_text(colour = "black"),
        panel.grid.major.y = element_line(colour = "grey92"))

School closure and opening dates

Sources:

date_schools_closed <- as.POSIXct("2020-03-16")
date_schools_opened <- as.POSIXct("2020-06-02")

Handle database connections

db_connect <- function() {
  db <- dbConnect(RSQLite::SQLite(), file.path("..", "data", "noordhoff.sqlite"))
  return(db)
}

db_disconnect <- function(db) {
  dbDisconnect(db)
}

Data

The database contains all SlimStampen data collected via Noordhoff’s platform in three courses: Stepping Stones (English), Grandes Lignes (French), and Neue Kontakte (German).

Trial-level response data are stored in the responses table. Book information, such as the course year, book title, and chapter, are stored in the book_info table.

responses

Column Type Explanation
date int UNIX time stamp [s]
user_id chr unique user identifier
method chr course
start_time int elapsed time since session start [ms]
rt int response time [ms]
duration int trial duration [ms]
fact_id int unique fact identifier (within chapter)
correct int response accuracy
answer chr user’s response
choices int number of answer choices (1 == open response)
backspace_used dbl user pressed backspace during trial
backspace_used_first dbl user erased first character of response
study int trial was a study trial
answer_language chr language of the answer
subsession int identifies part within learning session
book_info_id chr unique identifier of book information

book_info

Column Type Explanation
book_info_id chr unique identifier of book information
method_group chr year and edition
book_title chr book title (incl. year, level, edition)
book_type chr type of book
chapter chr chapter number and title

Preview first 10 rows

db <- db_connect()
responses_top <- dbGetQuery(db, "SELECT * FROM responses_noduplicates LIMIT 10")
responses_top
book_info_top <- dbGetQuery(db, "SELECT * FROM book_info LIMIT 10")
book_info_top
db_disconnect(db)

Usage

Get number of trials by method, day, and user:

db <- db_connect()
counts <- dbGetQuery(db,"SELECT r.method AS 'method',
                          DATE(r.date + 3600, 'unixepoch') AS 'doy',
                          r.user_id AS 'user',
                          COUNT(*) AS 'trials'
                          FROM 'responses_noduplicates' r
                          GROUP BY r.method,
                          DATE(r.date  + 3600, 'unixepoch'),
                          r.user_id
                        ")
db_disconnect(db)

setDT(counts)

Add a school year column (cutoff date: 1 August):

counts[, doy_posix := as.POSIXct(doy)]
counts[, school_year := ifelse(doy_posix < "2019-08-01", "18/19", "19/20")]

Add more sensible course names:

counts[, course := ifelse(method == "Grandes Lignes", "French", ifelse(method == "Stepping Stones", "English", "German"))]

Total number of unique users by course and school year

counts[, .(unique_users = length(unique(user))), by = .(course, school_year)]

There was some overlap between courses/school years, so we can’t simply add these numbers.

By course:

counts[, .(unique_users = length(unique(user))), by = .(course)]

By school year in the French and English courses:

counts[course %in% c("French", "English"), .(unique_users = length(unique(user))), by = .(school_year)]

And total number of unique users in the French and English sample across both years:

counts[course %in% c("French", "English"), .(unique_users = length(unique(user)))]

Total number of trials by course

counts[, .(total_trials = sum(trials)), by = .(course, school_year)]

Number of trials by user

counts[, trials_user := sum(trials), by = .(course, user)]
ggplot(counts, aes(x = trials_user)) +
  facet_wrap(~course, ncol = 1, scales = "free_y") +
  geom_histogram(binwidth = 100) +
  labs(x = "Number of trials by user",
       y = NULL) +
  theme_paper

Number of unique days by user

ggplot(counts[, .(N = length(unique(doy_posix))), by = c("user", "course")], aes(x = N)) +
  facet_grid(course ~ ., scales = "free_y") +
  geom_histogram(binwidth = 1) +
  labs(x = "Aantal dagen met leeractiviteit",
       y = "Aantal leerlingen") +
  theme_paper

Total number of trials by day

Interpolate missing days:

doy_posix <- seq.POSIXt(from = counts[,min(doy_posix)], to = counts[,max(doy_posix)], by = "DSTday")
course <- counts[,unique(course)]
dates <- CJ(doy_posix, course)
counts <- merge(counts, dates, by = c("doy_posix", "course"), all = TRUE)

Count trials by day:

counts[, trials_total := sum(trials, na.rm = TRUE), by = .(course, doy_posix)]
counts_by_day <- counts[, .(trials_total = sum(trials, na.rm = TRUE)), by = .(course, doy_posix)]
ggplot(counts_by_day[course %in% c("English", "French"),],
       aes(x = doy_posix, y = trials_total, colour = course)) +
  geom_line() +
  scale_x_datetime(date_breaks = "3 months", date_labels = "%e %b %Y") +
  scale_y_continuous(labels = number_format) +
  labs(x = NULL,
       y = "Number of trials per day",
       colour = "Course") +
  theme_paper

Total number of trials by week

Use cut.Date() to bin dates by week. Each day is assigned the date of the most recent Monday.

counts_by_day[, doy_posix_week := cut.POSIXt(doy_posix, "week")]
counts_by_day[, trials_total_week := sum(trials_total, na.rm = TRUE), by = .(course, doy_posix_week)]
ggplot(counts_by_day[course %in% c("English", "French"),],
            aes(x = doy_posix, y = trials_total_week, colour = course)) +
  geom_line() +
  scale_x_datetime(date_breaks = "3 months", date_labels = "%e %b %Y") +
  scale_y_continuous(labels = number_format) +
  labs(x = NULL,
       y = "Number of trials per week",
       colour = "Course") +
  theme_paper

Overlap the two school years:

counts_by_day[, school_year := ifelse(doy_posix < "2019-08-01", "18/19", "19/20")]
counts_by_day[school_year == "18/19", doy_posix_aligned := as.POSIXct(doy_posix + 365*24*60*60, origin = "1970-01-01")]
counts_by_day[school_year == "19/20", doy_posix_aligned := doy_posix]
p_trial_hist <- ggplot(counts_by_day[course %in% c("English", "French"),],
            aes(x = doy_posix_aligned, ymin = 0, ymax = trials_total_week, group = school_year, colour = school_year, fill = school_year)) +
  facet_wrap(~ course, ncol = 1) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -2e5, ymax = 2.2e6, fill = "grey92", colour = "grey50", lty = 2) +
  geom_ribbon(alpha = .2) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-09-01 02:00:00 CET",
                     "2019-11-01 02:00:00 CET",
                     "2020-01-01 02:00:00 CET",
                     "2020-03-01 02:00:00 CET",
                     "2020-05-01 02:00:00 CET",
                     "2020-07-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(expand = c(0, 0), limits = c(0, 2e6), labels = number_format) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Trials per week",
       colour = "School year",
       fill = "School year") +
  theme_paper

p_trial_hist

ggsave("../output/trial_hist.pdf", width = 5, height = 3)
ggsave("../output/trial_hist.eps", width = 5, height = 3)
Warning in grid.Call.graphics(C_polygon, x$x, x$y, index): semi-
transparency is not supported on this device: reported only once per page
ggsave("../output/trial_hist.png", width = 5, height = 3)

Make a line-plot version of the histogram.

p_trial_hist_line <- ggplot(counts_by_day[course %in% c("English", "French")],
            aes(x = doy_posix_aligned, y = trials_total_week, group = school_year, colour = school_year, fill = school_year)) +
  facet_wrap(~ course, ncol = 1) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -2e5, ymax = 2.2e6, fill = "grey92", colour = "grey50", lty = 2) +
  geom_line() +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-09-01 02:00:00 CET",
                     "2019-11-01 02:00:00 CET",
                     "2020-01-01 02:00:00 CET",
                     "2020-03-01 02:00:00 CET",
                     "2020-05-01 02:00:00 CET",
                     "2020-07-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(expand = c(0, 0), limits = c(0, 2e6), labels = number_format) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Trials per week",
       colour = "School year",
       fill = "School year") +
  theme_paper

p_trial_hist_line
Warning: Removed 101 row(s) containing missing values (geom_path).

ggsave("../output/trial_hist_line.pdf", width = 5, height = 3)
Warning: Removed 101 row(s) containing missing values (geom_path).
ggsave("../output/trial_hist_line.eps", width = 5, height = 3)
Warning: Removed 101 row(s) containing missing values (geom_path).
ggsave("../output/trial_hist_line.png", width = 5, height = 3)
Warning: Removed 101 row(s) containing missing values (geom_path).

Also make a difference plot.

# In order for the Mondays to align, move the 18/19 data forward by 1 year - 1 day.
counts_by_day[school_year == "18/19", doy_posix_aligned := as.POSIXct(doy_posix + 364*24*60*60, origin = "1970-01-01")]
counts_by_day[school_year == "19/20", doy_posix_aligned := doy_posix]
counts_by_day[, year_diff := trials_total_week[2] - trials_total_week[1], by = .(course, doy_posix_aligned)]
ggplot(counts_by_day[course %in% c("English", "French")],
            aes(x = doy_posix_aligned, y = year_diff)) +
  facet_wrap(~ course, ncol = 1) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -1e6, ymax = 1.1e6, fill = "grey92", colour = "grey50", lty = 2) +
  geom_hline(yintercept = 0, lty = 3) +
  geom_line() +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-09-01 02:00:00 CET",
                     "2019-11-01 02:00:00 CET",
                     "2020-01-01 02:00:00 CET",
                     "2020-03-01 02:00:00 CET",
                     "2020-05-01 02:00:00 CET",
                     "2020-07-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(expand = c(0, 0), limits = c(-3e5, 1e6), labels = number_format) +
  labs(x = NULL,
       y = "Trials per week",
       colour = "School year",
       fill = "School year") +
  theme_paper
Warning: Removed 101 row(s) containing missing values (geom_path).

Number of trials by user and week

counts[, doy_posix_week := cut.POSIXt(doy_posix, "week")]
counts_by_user_and_week <- counts[, .(trials_user = sum(trials, na.rm = TRUE)), by = .(course, school_year, user, doy_posix_week)]

Save for clustering analysis

saveRDS(na.omit(counts_by_user_and_week[course %in% c("English", "French")]), "../data/trials_by_user_and_week.rds")

Unique users by day

users_by_day <- counts[, .(unique_users = length(unique(user))), by = .(course, doy_posix)]
p <- ggplot(users_by_day, aes(x = doy_posix, y = unique_users, colour = course)) +
  geom_line() +
  scale_x_datetime(date_breaks = "3 months", date_labels = "%e %b %Y") +
  scale_y_continuous(labels = number_format) +
  labs(x = NULL,
       y = "Number of users per day",
       colour = "Course") +
  theme_paper

p

Unique users by week

Use cut.Date() to bin dates by week. Each day is assigned the date of the most recent Monday.

users_by_day[, doy_posix_week := cut.POSIXt(doy_posix, "week")]
users_by_week <- counts[, .(unique_users_week = length(unique(user))), by = .(course, doy_posix_week)]
users_by_week <- users_by_day[users_by_week, on = .(course, doy_posix_week)]
p <- ggplot(users_by_week, aes(x = doy_posix, ymin = 0, ymax = unique_users_week, group = course, colour = course, fill = course)) +
  facet_wrap(~ course, ncol = 1, scales = "free_y") +
  geom_ribbon(alpha = .2) +
  scale_x_datetime(date_breaks = "3 months", date_labels = "%e %b %Y") +
  scale_y_continuous(labels = scales::number_format(big.mark = ".", decimal.mark = ",")) +
  labs(x = NULL,
       y = "Aantal gebruikers",
       title = "Aantal verschillende gebruikers per week",
       caption = "Let op: schaal verschilt tussen de grafieken",
       colour = "Lesmethode",
       fill = "Lesmethode") +
  guides(colour = FALSE, fill = FALSE) +
  theme_paper

p

Overlap the two school years:

users_by_week[, school_year := ifelse(doy_posix < "2019-08-01", "18/19", "19/20")]
users_by_week[school_year == "18/19", doy_posix_aligned := as.POSIXct(doy_posix + 365*24*60*60, origin = "1970-01-01")]
users_by_week[school_year == "19/20", doy_posix_aligned := doy_posix]
p_user_hist <- ggplot(users_by_week[course %in% c("English", "French"),],
            aes(x = doy_posix_aligned, ymin = 0, ymax = unique_users_week, group = school_year, colour = school_year, fill = school_year)) +
  facet_wrap(~ course, ncol = 1) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -800, ymax = 8800, fill = "grey92", colour = "grey50", lty = 2) +
  geom_ribbon(alpha = .2) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-09-01 02:00:00 CET",
                     "2019-11-01 02:00:00 CET",
                     "2020-01-01 02:00:00 CET",
                     "2020-03-01 02:00:00 CET",
                     "2020-05-01 02:00:00 CET",
                     "2020-07-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(expand = c(0, 0), limits = c(0, 8000), labels = number_format) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Unique users per week",
       colour = "School year",
       fill = "School year") +
  theme_paper

p_user_hist

ggsave("../output/user_hist.pdf", width = 5, height = 3)
ggsave("../output/user_hist.eps", width = 5, height = 3)
Warning in grid.Call.graphics(C_polygon, x$x, x$y, index): semi-
transparency is not supported on this device: reported only once per page
ggsave("../output/user_hist.png", width = 5, height = 3)

Make a combined plot of trial and user counts for in the paper:

p_legend <- get_legend(p_trial_hist)

p_trial_hist <- p_trial_hist +
  guides(colour = FALSE, fill = FALSE)

p_user_hist <- p_user_hist +
  guides(colour = FALSE, fill = FALSE)
plot_grid(plot_grid(p_trial_hist, p_user_hist,
          labels = c("A", "B"),
          align = "v", axis = "tblr"),
          p_legend,
          rel_widths = c(1, .2))

ggsave("../output/combi_hist.pdf", width = 9, height = 3)
ggsave("../output/combi_hist.eps", width = 9, height = 3)
Warning in grid.Call.graphics(C_polygon, x$x, x$y, index): semi-
transparency is not supported on this device: reported only once per page
ggsave("../output/combi_hist.png", width = 9, height = 3)

Activity during the week

Get number of trials by method, day, hour, and user:

db <- db_connect()
counts_by_hour <- dbGetQuery(db,"SELECT r.method AS 'method',
                          DATE(r.date + 3600, 'unixepoch') AS 'doy',
                          STRFTIME('%H', r.date + 3600, 'unixepoch') AS 'hour',
                          r.user_id AS 'user',
                          COUNT(*) AS 'trials'
                          FROM 'responses_noduplicates' r
                          GROUP BY r.method,
                          DATE(r.date + 3600, 'unixepoch'),
                          STRFTIME('%H', r.date + 3600, 'unixepoch'),
                          r.user_id
                        ")
db_disconnect(db)

setDT(counts_by_hour)

Interpolate missing days and hours:

counts_by_hour[, doy_posix := as.POSIXct(doy)]
counts_by_hour[, hour := as.numeric(hour)]
doy_posix <- seq.POSIXt(from = counts_by_hour[,min(doy_posix)], to = counts_by_hour[,max(doy_posix)], by = "DSTday")
method <- counts_by_hour[,unique(method)]
hour <- 0:23
dates_and_hours <- CJ(doy_posix, hour, method)
counts_by_hour <- merge(counts_by_hour, dates_and_hours, by = c("doy_posix", "hour", "method"), all = TRUE)

Add day of the week:

counts_by_hour[, weekday := weekdays(doy_posix)]

Distinguish between school years:

counts_by_hour[, school_year := ifelse(doy_posix < "2019-08-01", "18/19", "19/20")]

Add quarter:

counts_by_hour[, quarter := paste0(year(doy_posix), "Q", quarter(doy_posix))]

Add exact school closure period in both school years:

counts_by_hour[school_year == "18/19", doy_posix_aligned := as.POSIXct(doy_posix + 365*24*60*60, origin = "1970-01-01")]
counts_by_hour[school_year == "19/20", doy_posix_aligned := doy_posix]
counts_by_hour[, schools_closed := doy_posix_aligned >= date_schools_closed & doy_posix_aligned < date_schools_opened]

Add more sensible course names:

counts_by_hour[, course := ifelse(method == "Grandes Lignes", "French", ifelse(method == "Stepping Stones", "English", "German"))]

Sum trials by school year, weekday and hour:

counts_by_hour[, trials_schoolyear := sum(trials, na.rm = TRUE), by = .(course, school_year, weekday, hour)]

Also sum trials by quarter, weekday and hour:

counts_by_hour[, trials_quarter := sum(trials, na.rm = TRUE), by = .(course, quarter, weekday, hour)]

And sum trials within the closure period by weekday and hour:

counts_by_hour[schools_closed == TRUE, trials_closed := sum(trials, na.rm = TRUE), by = .(course, school_year, weekday, hour)]
trials_by_wday_hour <- unique(counts_by_hour, by = c("course", "school_year", "quarter", "schools_closed", "weekday", "hour"))

trials_by_wday_hour[, trials_normalised_schoolyear := trials_schoolyear / sum(trials_schoolyear), by = .(course)]
trials_by_wday_hour[, trials_normalised_quarter := trials_quarter / sum(trials_quarter), by = .(course)]
trials_by_wday_hour[, weekday := ordered(weekday, levels = c("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"))]
# trials_by_wday_hour[, weekday := ordered(weekday, levels = c("maandag", "dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag", "zondag"))]

Plot heatmap for the whole school year:

ggplot(trials_by_wday_hour[course %in% c("English", "French")],
       aes(x = hour, y = reorder(weekday, dplyr::desc(weekday)), fill = trials_normalised_schoolyear)) + 
  facet_grid(school_year ~ course) +
  geom_tile(colour = "white", size = 0.25) +
  labs(x = "Time of day (hour)",
       y = NULL) +
  scale_x_continuous(expand = c(0,0), breaks = seq(0, 24, 3)) +
  scale_y_discrete(expand = c(0,0)) + 
  scale_fill_viridis_c(option = "A", direction = -1) +
  coord_fixed() +
  guides(fill = FALSE) +
  theme_paper

Plot heatmap per quarter:

ggplot(trials_by_wday_hour, aes(x = hour, y = reorder(weekday, dplyr::desc(weekday)), fill = trials_normalised_quarter)) + 
  facet_grid(quarter ~ method) +
  geom_tile(colour = "white", size = 0.25) +
  labs(x = NULL,
       y = NULL,
       title = "Activiteit per uur gedurende de week",
       caption = "Aantal trials per weekdag en uur in elk kwartaal, genormaliseerd per methode.") +
  scale_x_continuous(expand = c(0,0), breaks = seq(0, 24, 3)) +
  scale_y_discrete(expand = c(0,0)) + 
  scale_fill_viridis_c(option = "A", direction = -1) +
  coord_fixed() +
  guides(fill = FALSE) +
  theme_bw(base_size = 16)

Plot heatmap for the period in which schools were closed:

trials_closed <- unique(trials_by_wday_hour[schools_closed == TRUE, .(course, school_year, weekday, hour, trials_closed)])

trials_closed[, trials_normalised_closed := trials_closed / sum(trials_closed), by = .(course, school_year)]
trials_closed_diff <- trials_closed[, .(school_year = "Change",
                                        trials_closed = trials_closed[school_year == "19/20"] - trials_closed[school_year == "18/19"],
                                        trials_normalised_closed = trials_normalised_closed[school_year == "19/20"] - trials_normalised_closed[school_year == "18/19"]), by = .(course, weekday, hour)]
p_heatmap <- ggplot(trials_closed[course %in% c("English", "French"),],
       aes(x = hour, y = reorder(weekday, dplyr::desc(weekday)), fill = trials_normalised_closed)) + 
  facet_grid(school_year ~ course) +
  geom_tile(colour = "white", size = 0.25) +
  labs(x = "Time of day (hour)",
       y = NULL,
       fill = NULL) +
  scale_x_continuous(expand = c(0,0), breaks = seq(0, 24, 3)) +
  scale_y_discrete(expand = c(0,0)) + 
  scale_fill_viridis_c(option = "A", direction = -1) +
  coord_fixed() +
  theme_paper


p_heatmap

Make a plot of the difference between the two school years during the school closure period:

p_heatmap_diff <- ggplot(trials_closed_diff[course %in% c("English", "French"),],
       aes(x = hour, y = reorder(weekday, dplyr::desc(weekday)), fill = trials_normalised_closed)) + 
  facet_grid(school_year ~ course) +
  geom_tile(colour = "white", size = 0.25) +
  labs(x = "Time of day (hour)",
       y = NULL,
       fill = NULL) +
  scale_x_continuous(expand = c(0,0), breaks = seq(0, 24, 3)) +
  scale_y_discrete(expand = c(0,0)) + 
  scale_fill_distiller(type = "div", palette = "RdBu", direction = -1, limits = c(-1, 1) * max(abs(trials_closed_diff[course %in% c("English", "French"),]$trials_normalised_closed))) +
  coord_fixed() +
  theme_paper

p_heatmap_diff

Make a combined plot for in the paper:

p_heatmap_legend <- get_legend(p_heatmap)
p_heatmap_diff_legend <- get_legend(p_heatmap_diff)

p_heatmap <- p_heatmap + guides(fill = FALSE)
p_heatmap_diff <- p_heatmap_diff + guides(fill = FALSE)
plot_grid(
  plot_grid(p_heatmap, p_heatmap_diff,
          ncol = 1,
          labels = c("A", "B"),
          rel_heights = c(1, .655)
          ),
  plot_grid(p_heatmap_legend, p_heatmap_diff_legend,
            ncol = 1,
            align = "vh", axis = "lrtb"),
  ncol = 2,
  rel_widths = c(1, .15))

ggsave("../output/combi_heatmap.pdf", width = 9, height = 4.5)
ggsave("../output/combi_heatmap.eps", width = 9, height = 4.5)
ggsave("../output/combi_heatmap.png", width = 9, height = 4.5)

Activity stratified by year and level

db <- db_connect()
counts_strat <- dbGetQuery(db,"SELECT r.method AS 'method',
                          r.book_info_id as 'book_info_id',
                          DATE(r.date + 3600, 'unixepoch') AS 'doy',
                          r.user_id AS 'user',
                          COUNT(*) AS 'trials'
                          FROM 'responses_noduplicates' r
                          GROUP BY r.method,
                          r.book_info_id,
                          DATE(r.date + 3600, 'unixepoch'),
                          r.user_id
                        ")
db_disconnect(db)

setDT(counts_strat)
db <- db_connect()
book_info <- dbGetQuery(db, "SELECT * FROM 'book_info'")
db_disconnect(db)

setDT(book_info)

Add book information:

counts_strat[book_info, on = "book_info_id", c("book_title", "method_group") := .(i.book_title, i.method_group)]

Add a school year column (cutoff date: 1 August):

counts_strat[, doy_posix := as.POSIXct(doy)]
counts_strat[, school_year := ifelse(doy_posix < "2019-08-01", "18/19", "19/20")]

Add sensible course names:

counts_strat[, course := ifelse(method == "Grandes Lignes", "French", ifelse(method == "Stepping Stones", "English", "German"))]

Count trials by day:

counts_strat_by_day <- counts_strat[, .(trials_total = sum(trials, na.rm = TRUE)), by = .(school_year, course, method_group, book_title, doy_posix)]
setorder(counts_strat_by_day, school_year, course, method_group, book_title, doy_posix)

Simplify level names:

# Keep all distinctions
counts_strat_by_day[, book_title_simple := stringr::str_sub(book_title, 3, -10)]
counts_strat_by_day[, book_title_simple := factor(book_title_simple, levels = c("vmbo b/lwoo", "vmbo b", "vmbo bk", "vmbo k", "vmbo kgt", "vmbo-gt", "vmbo gt", "vmbo-gt/havo", "vmbo (t)hv", "havo", "havo vwo", "vwo"))]
# Simplify to three levels
counts_strat_by_day[, level := dplyr::case_when(
  grepl( "hv", book_title) ~ "General secondary\n(havo)",
  grepl("vmbo", book_title) ~ "Pre-vocational\n(vmbo)",
  grepl("havo", book_title) ~ "General secondary\n(havo)",
  grepl("vwo", book_title) ~ "Pre-university\n(vwo)",
  TRUE ~ "Other")]
counts_strat_by_day[, level := factor(level, levels = c("Other", "Pre-vocational\n(vmbo)", "General secondary\n(havo)", "Pre-university\n(vwo)"))]

Simplify year names:

counts_strat_by_day[, year := dplyr::case_when(
  method_group == "Leerjaar 1 (5e Ed.)" ~ "Year 1",
  method_group == "Leerjaar 2 (5e Ed.)" ~ "Year 2",
  method_group == "Leerjaar 3 (5e Ed.)" ~ "Year 3",
  method_group == "Leerjaar 3/4 (5e Ed.)" ~ "Year 3/4",
  method_group == "Leerjaar 4 (5e Ed.)" ~ "Year 4",
  method_group == "Tweede Fase (6e Ed.)" ~ "Tweede Fase",
  TRUE ~ "Other")]

Align school years:

counts_strat_by_day[school_year == "18/19", doy_posix_aligned := as.POSIXct(doy_posix + 365*24*60*60, origin = "1970-01-01")]
counts_strat_by_day[school_year == "19/20", doy_posix_aligned := doy_posix]

Use cut.Date() to bin dates by week. Each day is assigned the date of the most recent Monday.

counts_strat_by_day[, doy_posix_aligned_week := cut.POSIXt(doy_posix_aligned, "week")]
counts_strat_by_day[, trials_total_week := sum(trials_total, na.rm = TRUE), by = .(school_year, course, method_group, book_title_simple, doy_posix_aligned_week)]
counts_strat_by_day[, trials_total_week_level := sum(trials_total), by = .(school_year, course, method_group, level, doy_posix_aligned_week)]

Summarise increase during lockdown:

counts_strat_increase <- counts_strat_by_day[between(doy_posix_aligned, date_schools_closed, date_schools_opened), .(trials_lockdown = sum(trials_total)), by = .(course, book_title_simple, method_group, year, school_year)]
counts_strat_increase[, increase := trials_lockdown[2]/trials_lockdown[1], by = .(course, book_title_simple, method_group, year)]
counts_strat_increase[, increase_pct := paste0("Change:\n", scales::percent(increase, accuracy = 2))]
counts_strat_increase_level <- counts_strat_by_day[between(doy_posix_aligned, date_schools_closed, date_schools_opened), .(trials_lockdown = sum(trials_total)), by = .(course, level, method_group, year, school_year)]
counts_strat_increase_level[, increase := trials_lockdown[2]/trials_lockdown[1], by = .(course, level, method_group, year)]
counts_strat_increase_level[, increase_pct := paste0("Change:\n", scales::percent(increase, accuracy = 2))]

French

ggplot(counts_strat_by_day[course == "French"], 
       aes(group = school_year, colour = school_year, fill = school_year)) +
  facet_grid(book_title_simple ~ method_group) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -2e5, ymax = 2.2e6, fill = "grey92", colour = "grey50", lty = 2) +
  geom_ribbon(aes(x = doy_posix_aligned, ymin = 0, ymax = trials_total_week, ), alpha = .2) +
  geom_text(data = counts_strat_increase[course == "French" & school_year == "19/20"], 
            aes(label = increase_pct),
            x = as.POSIXct((as.numeric(date_schools_closed) + as.numeric(date_schools_opened))/2, origin = "1970-01-01"),
            y = 3.6e5,
            colour = "black",
            vjust = 1,
            show.legend = FALSE) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-09-01 02:00:00 CET",
                     "2019-11-01 02:00:00 CET",
                     "2020-01-01 02:00:00 CET",
                     "2020-03-01 02:00:00 CET",
                     "2020-05-01 02:00:00 CET",
                     "2020-07-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(expand = c(0, 0), limits = c(0, 3.75e5), labels = number_format) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Trials per week",
       colour = "School year",
       fill = "School year",
       title = "French") +
  theme_paper

ggsave("../output/trial_hist_french.pdf", width = 14, height = 10)
ggsave("../output/trial_hist_french.eps", width = 14, height = 10)
Warning in grid.Call.graphics(C_polygon, x$x, x$y, index): semi-
transparency is not supported on this device: reported only once per page
ggsave("../output/trial_hist_french.png", width = 14, height = 10)

Streamlined version for in the paper:

ggplot(counts_strat_by_day[course == "French"], 
       aes(group = school_year, colour = school_year, fill = school_year)) +
  facet_grid(level ~ year) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -2e5, ymax = 2.2e6, fill = "grey92", colour = "grey50", lty = 2) +
  geom_ribbon(aes(x = doy_posix_aligned, ymin = 0, ymax = trials_total_week_level, ), alpha = .2) +
  geom_text(data = counts_strat_increase_level[course == "French" & school_year == "19/20"], 
            aes(label = increase_pct),
            x = as.POSIXct((as.numeric(date_schools_closed) + as.numeric(date_schools_opened))/2, origin = "1970-01-01"),
            y = 3.6e5,
            colour = "black",
            vjust = 1,
            size = rel(2.75),
            show.legend = FALSE) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(expand = c(0, 0), limits = c(0, 3.75e5), labels = number_format) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Trials per week",
       colour = "School year",
       fill = "School year") +
  theme_paper

ggsave("../output/trial_hist_french_level.pdf", width = 9, height = 5)
ggsave("../output/trial_hist_french_level.eps", width = 9, height = 5)
Warning in grid.Call.graphics(C_polygon, x$x, x$y, index): semi-
transparency is not supported on this device: reported only once per page
ggsave("../output/trial_hist_french_level.png", width = 9, height = 5)

There are two reasons why total trial count may increase: there are more active users, or active users complete more trials. Given the fixed curriculum, the first reason seems more plausible. We can confirm this by plotting the weekly number of trials per user.

users_strat <- copy(counts_strat)

users_strat[, book_title_simple := stringr::str_sub(book_title, 3, -10)]
users_strat[, book_title_simple := factor(book_title_simple, levels = c("vmbo b/lwoo", "vmbo b", "vmbo bk", "vmbo k", "vmbo kgt", "vmbo-gt", "vmbo gt", "vmbo-gt/havo", "vmbo (t)hv", "havo", "havo vwo", "vwo"))]
# Simplify to three levels
users_strat[, level := dplyr::case_when(
  grepl( "hv", book_title) ~ "General secondary\n(havo)",
  grepl("vmbo", book_title) ~ "Pre-vocational\n(vmbo)",
  grepl("havo", book_title) ~ "General secondary\n(havo)",
  grepl("vwo", book_title) ~ "Pre-university\n(vwo)",
  TRUE ~ "Other")]
users_strat[, level := factor(level, levels = c("Other", "Pre-vocational\n(vmbo)", "General secondary\n(havo)", "Pre-university\n(vwo)"))]
users_strat[, year := dplyr::case_when(
  method_group == "Leerjaar 1 (5e Ed.)" ~ "Year 1",
  method_group == "Leerjaar 2 (5e Ed.)" ~ "Year 2",
  method_group == "Leerjaar 3 (5e Ed.)" ~ "Year 3",
  method_group == "Leerjaar 3/4 (5e Ed.)" ~ "Year 3/4",
  method_group == "Leerjaar 4 (5e Ed.)" ~ "Year 4",
  method_group == "Tweede Fase (6e Ed.)" ~ "Tweede Fase",
  TRUE ~ "Other")]
users_strat[school_year == "18/19", doy_posix_aligned := as.POSIXct(doy_posix + 365*24*60*60, origin = "1970-01-01")]
users_strat[school_year == "19/20", doy_posix_aligned := doy_posix]
users_strat[, doy_posix_aligned_week := cut.POSIXt(doy_posix_aligned, "week")]
users_strat_by_week <- users_strat[, .(users_week_level = .N), by = .(school_year, course, year, level, doy_posix_aligned_week)]
counts_strat_by_day <- counts_strat_by_day[users_strat_by_week, on = c("school_year", "course", "year", "level", "doy_posix_aligned_week")][, trials_per_user_week := trials_total_week_level / users_week_level]
ggplot(counts_strat_by_day[course == "French"], 
       aes(group = school_year, colour = school_year, fill = school_year)) +
  facet_grid(level ~ year) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -2e5, ymax = 2.2e6, fill = "grey92", colour = "grey50", lty = 2) +
  geom_ribbon(aes(x = doy_posix_aligned, ymin = 0, ymax = trials_per_user_week, ), alpha = .2) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(expand = c(0, 0), labels = number_format) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Trials per user per week",
       colour = "School year",
       fill = "School year") +
  theme_paper

As expected, there is essentially no change in the number of trials each user completes, so the increased trial count that we see comes from more users being active.

English

ggplot(counts_strat_by_day[course == "English"], 
       aes(group = school_year, colour = school_year, fill = school_year)) +
  facet_grid(book_title_simple ~ method_group) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -2e5, ymax = 2.2e6, fill = "grey92", colour = "grey50", lty = 2) +
  geom_ribbon(aes(x = doy_posix_aligned, ymin = 0, ymax = trials_total_week, ), alpha = .2) +
  geom_text(data = counts_strat_increase[course == "English" & school_year == "19/20"], 
            aes(label = increase_pct),
            x = as.POSIXct((as.numeric(date_schools_closed) + as.numeric(date_schools_opened))/2, origin = "1970-01-01"),
            y = 3.6e5,
            colour = "black",
            vjust = 1,
            show.legend = FALSE) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-09-01 02:00:00 CET",
                     "2019-11-01 02:00:00 CET",
                     "2020-01-01 02:00:00 CET",
                     "2020-03-01 02:00:00 CET",
                     "2020-05-01 02:00:00 CET",
                     "2020-07-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(expand = c(0, 0), limits = c(0, 3.75e5), labels = number_format) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Trials per week",
       colour = "School year",
       fill = "School year",
       title = "English") +
  theme_paper

ggsave("../output/trial_hist_english.pdf", width = 14, height = 10)
ggsave("../output/trial_hist_english.eps", width = 14, height = 10)
Warning in grid.Call.graphics(C_polygon, x$x, x$y, index): semi-
transparency is not supported on this device: reported only once per page
ggsave("../output/trial_hist_english.png", width = 14, height = 10)

Streamlined version for in the paper:

ggplot(counts_strat_by_day[course == "English" & level != "Other"], 
       aes(group = school_year, colour = school_year, fill = school_year)) +
  facet_grid(level ~ year) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -2e5, ymax = 2.2e6, fill = "grey92", colour = "grey50", lty = 2) +
  geom_ribbon(aes(x = doy_posix_aligned, ymin = 0, ymax = trials_total_week_level, ), alpha = .2) +
  geom_text(data = counts_strat_increase_level[course == "English" & level != "Other" & school_year == "19/20"], 
            aes(label = increase_pct),
            x = as.POSIXct((as.numeric(date_schools_closed) + as.numeric(date_schools_opened))/2, origin = "1970-01-01"),
            y = 9.6e5,
            colour = "black",
            vjust = 1,
            size = rel(2.75),
            show.legend = FALSE) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(expand = c(0, 0), limits = c(0, 1e6), labels = number_format) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Trials per week",
       colour = "School year",
       fill = "School year") +
  theme_paper

ggsave("../output/trial_hist_english_level.pdf", width = 9, height = 5)
ggsave("../output/trial_hist_english_level.eps", width = 9, height = 5)
Warning in grid.Call.graphics(C_polygon, x$x, x$y, index): semi-
transparency is not supported on this device: reported only once per page
ggsave("../output/trial_hist_english_level.png", width = 9, height = 5)

Also plot the weekly number of trials per user. As with French, this number stays more or less constant:

ggplot(counts_strat_by_day[course == "English"], 
       aes(group = school_year, colour = school_year, fill = school_year)) +
  facet_grid(level ~ year) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -2e5, ymax = 2.2e6, fill = "grey92", colour = "grey50", lty = 2) +
  geom_ribbon(aes(x = doy_posix_aligned, ymin = 0, ymax = trials_per_user_week, ), alpha = .2) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(expand = c(0, 0), labels = number_format) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Trials per user per week",
       colour = "School year",
       fill = "School year") +
  theme_paper

Question type

There are different question formats: open-answer, in which the student types the answer, and multiple-choice, in which the student selects the answer from a set of 3 or 4 options.

db <- db_connect()
question_type <- dbGetQuery(db, 
                      "SELECT r.method AS 'method',
                      DATE(r.date + 3600, 'unixepoch') AS 'doy',
                      r.choices AS 'choices',
                      COUNT(*) AS 'n'
                      FROM 'responses_noduplicates' r
                      WHERE r.study == 0
                      GROUP BY r.method,
                      DATE(r.date + 3600, 'unixepoch'),
                      r.choices"
)
setDT(question_type)
db_disconnect(db)

Add a school year column (cutoff date: 1 August):

question_type[, doy_posix := as.POSIXct(doy)]
question_type[, school_year := ifelse(doy_posix < "2019-08-01", "18/19", "19/20")]

Add sensible course names:

question_type[, course := ifelse(method == "Grandes Lignes", "French", ifelse(method == "Stepping Stones", "English", "German"))]

Align school years:

question_type[school_year == "18/19", doy_posix_aligned := as.POSIXct(doy_posix + 365*24*60*60, origin = "1970-01-01")]
question_type[school_year == "19/20", doy_posix_aligned := doy_posix]

Use cut.Date() to bin dates by week. Each day is assigned the date of the most recent Monday.

question_type[, doy_posix_week := cut.POSIXt(doy_posix, "week")]
question_type[, doy_posix_aligned_week := cut.POSIXt(doy_posix_aligned, "week")]
question_type_by_week <- question_type[, .(n = sum(n)), by = .(course, school_year, doy_posix_aligned_week, choices)]
ggplot(question_type_by_week[course %in% c("English", "French")], aes(x = as.POSIXct(doy_posix_aligned_week), y = n, group = interaction(school_year,as.factor(choices)), colour = school_year)) +
  facet_grid(course ~ choices) +
  geom_line() +
  scale_x_datetime(expand = c(0, 0),
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  labs(x = NULL,
       y = "Trials",
       colour = "School year") +
  theme_paper
Warning: Removed 24 row(s) containing missing values (geom_path).

question_type[, .(n = sum(n)), by = .(course, mcq = choices>1, school_year)][, .(perc_mcq = n[mcq == TRUE]/sum(n)), by = .(course, school_year)]

There is a clear difference between the languages in the question format used: English uses almost exclusively 4-alternative MCQs, while French uses a mix of MCQs (including a small number of 3-alternative questions) and open-answer questions.

Session info

sessionInfo()
R version 3.6.3 (2020-02-29)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.04.5 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.7.1
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.7.1

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=nl_NL.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=nl_NL.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=nl_NL.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] grid      stats     graphics  grDevices utils     datasets  methods  
[8] base     

other attached packages:
[1] cowplot_0.9.4     ggplot2_3.3.2     DBI_1.1.0         data.table_1.13.6

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.6         RColorBrewer_1.1-2 pillar_1.4.2      
 [4] compiler_3.6.3     tools_3.6.3        digest_0.6.19     
 [7] bit_1.1-14         jsonlite_1.6       viridisLite_0.3.0 
[10] memoise_1.1.0      evaluate_0.14      RSQLite_2.2.0     
[13] tibble_2.1.3       gtable_0.3.0       pkgconfig_2.0.2   
[16] rlang_0.4.10       yaml_2.2.0         xfun_0.21         
[19] withr_2.3.0        stringr_1.4.0      dplyr_0.8.3       
[22] knitr_1.23         vctrs_0.2.2        bit64_0.9-7       
[25] tidyselect_0.2.5   glue_1.3.1         R6_2.4.0          
[28] rmarkdown_2.6      purrr_0.3.2        blob_1.2.1        
[31] magrittr_1.5       scales_1.0.0       htmltools_0.3.6   
[34] assertthat_0.2.1   colorspace_1.4-1   labeling_0.3      
[37] stringi_1.4.3      munsell_0.5.0      crayon_1.3.4      
LS0tCnRpdGxlOiAnU2xpbVN0YW1wZW4gVXNhZ2UgRHVyaW5nIExvY2tkb3duJwphdXRob3I6ICJNYWFydGVuIHZhbiBkZXIgVmVsZGUiCmRhdGU6ICJMYXN0IHVwZGF0ZWQ6IGByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgZ2l0aHViX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICBodG1sX25vdGVib29rOgogICAgc21hcnQ6IG5vCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKZWRpdG9yX29wdGlvbnM6IAogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUKLS0tCgojIFNldHVwCgpgYGB7cn0KbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KERCSSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGNvd3Bsb3QpCmxpYnJhcnkoZ3JpZCkKClN5cy5zZXRsb2NhbGUoIkxDX1RJTUUiLCAiZW5fVVMuVVRGLTgiKSAjIFByaW50IEVuZ2xpc2ggZGF0ZSBmb3JtYXQKIyBTeXMuc2V0bG9jYWxlKCJMQ19USU1FIiwgIm5sX05MLlVURi04IikgIyBQcmludCBEdXRjaCBkYXRlIGZvcm1hdAoKbnVtYmVyX2Zvcm1hdCA8LSBzY2FsZXM6Om51bWJlcl9mb3JtYXQoYmlnLm1hcmsgPSAiLCIsIGRlY2ltYWwubWFyayA9ICIuIikgIyBQcmludCBFbmdsaXNoIG51bWJlciBmb3JtYXQKIyBudW1iZXJfZm9ybWF0IDwtIHNjYWxlczo6bnVtYmVyX2Zvcm1hdChiaWcubWFyayA9ICIuIiwgZGVjaW1hbC5tYXJrID0gIiwiKSAjIFByaW50IER1dGNoIG51bWJlciBmb3JtYXQKCnRoZW1lX3BhcGVyIDwtIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTIpICsgCiAgdGhlbWUoYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KGNvbG91ciA9ICJibGFjayIpLAogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAiZ3JleTkyIikpCmBgYAoKU2Nob29sIGNsb3N1cmUgYW5kIG9wZW5pbmcgZGF0ZXMKClNvdXJjZXM6CgogIC0gaHR0cHM6Ly93d3cucmlqa3NvdmVyaGVpZC5ubC9hY3R1ZWVsL25pZXV3cy8yMDIwLzAzLzE1L2FhbnZ1bGxlbmRlLW1hYXRyZWdlbGVuLW9uZGVyd2lqcy1ob3JlY2Etc3BvcnQKICAtIGh0dHBzOi8vd3d3LnJpamtzb3ZlcmhlaWQubmwvYWN0dWVlbC9uaWV1d3MvMjAyMC8wNS8xOS9vbmRlcndpanMtZ2FhdC1zdGFwLXZvb3Itc3RhcC1vcGVuCgpgYGB7cn0KZGF0ZV9zY2hvb2xzX2Nsb3NlZCA8LSBhcy5QT1NJWGN0KCIyMDIwLTAzLTE2IikKZGF0ZV9zY2hvb2xzX29wZW5lZCA8LSBhcy5QT1NJWGN0KCIyMDIwLTA2LTAyIikKYGBgCgoKSGFuZGxlIGRhdGFiYXNlIGNvbm5lY3Rpb25zCmBgYHtyfQpkYl9jb25uZWN0IDwtIGZ1bmN0aW9uKCkgewogIGRiIDwtIGRiQ29ubmVjdChSU1FMaXRlOjpTUUxpdGUoKSwgZmlsZS5wYXRoKCIuLiIsICJkYXRhIiwgIm5vb3JkaG9mZi5zcWxpdGUiKSkKICByZXR1cm4oZGIpCn0KCmRiX2Rpc2Nvbm5lY3QgPC0gZnVuY3Rpb24oZGIpIHsKICBkYkRpc2Nvbm5lY3QoZGIpCn0KYGBgCgoKIyBEYXRhCgpUaGUgZGF0YWJhc2UgY29udGFpbnMgYWxsIFNsaW1TdGFtcGVuIGRhdGEgY29sbGVjdGVkIHZpYSBOb29yZGhvZmYncyBwbGF0Zm9ybSBpbiB0aHJlZSBjb3Vyc2VzOiAqU3RlcHBpbmcgU3RvbmVzKiAoRW5nbGlzaCksICpHcmFuZGVzIExpZ25lcyogKEZyZW5jaCksIGFuZCAqTmV1ZSBLb250YWt0ZSogKEdlcm1hbikuCgpUcmlhbC1sZXZlbCByZXNwb25zZSBkYXRhIGFyZSBzdG9yZWQgaW4gdGhlIGByZXNwb25zZXNgIHRhYmxlLgpCb29rIGluZm9ybWF0aW9uLCBzdWNoIGFzIHRoZSBjb3Vyc2UgeWVhciwgYm9vayB0aXRsZSwgYW5kIGNoYXB0ZXIsIGFyZSBzdG9yZWQgaW4gdGhlIGBib29rX2luZm9gIHRhYmxlLgoKIyMgYHJlc3BvbnNlc2AKCnwgQ29sdW1uICAgICAgICAgICAgICAgfCBUeXBlICAgICAgfCBFeHBsYW5hdGlvbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8LS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwKfCBkYXRlICAgICAgICAgICAgICAgICB8IGludCAgICAgICB8IFVOSVggdGltZSBzdGFtcCBbc10gICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgdXNlcl9pZCAgICAgICAgICAgICAgfCBjaHIgICAgICAgfCB1bmlxdWUgdXNlciBpZGVudGlmaWVyICAgICAgICAgICAgICAgICAgICAgICAgfAp8IG1ldGhvZCAgICAgICAgICAgICAgIHwgY2hyICAgICAgIHwgY291cnNlICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBzdGFydF90aW1lICAgICAgICAgICB8IGludCAgICAgICB8IGVsYXBzZWQgdGltZSBzaW5jZSBzZXNzaW9uIHN0YXJ0IFttc10gICAgICAgICB8CnwgcnQgICAgICAgICAgICAgICAgICAgfCBpbnQgICAgICAgfCByZXNwb25zZSB0aW1lIFttc10gICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IGR1cmF0aW9uICAgICAgICAgICAgIHwgaW50ICAgICAgIHwgdHJpYWwgZHVyYXRpb24gW21zXSAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBmYWN0X2lkICAgICAgICAgICAgICB8IGludCAgICAgICB8IHVuaXF1ZSBmYWN0IGlkZW50aWZpZXIgKHdpdGhpbiBjaGFwdGVyKSAgICAgICB8CnwgY29ycmVjdCAgICAgICAgICAgICAgfCBpbnQgICAgICAgfCByZXNwb25zZSBhY2N1cmFjeSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IGFuc3dlciAgICAgICAgICAgICAgIHwgY2hyICAgICAgIHwgdXNlcidzIHJlc3BvbnNlICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBjaG9pY2VzICAgICAgICAgICAgICB8IGludCAgICAgICB8IG51bWJlciBvZiBhbnN3ZXIgY2hvaWNlcyAoMSA9PSBvcGVuIHJlc3BvbnNlKSB8CnwgYmFja3NwYWNlX3VzZWQgICAgICAgfCBkYmwgICAgICAgfCB1c2VyIHByZXNzZWQgYmFja3NwYWNlIGR1cmluZyB0cmlhbCAgICAgICAgICAgfAp8IGJhY2tzcGFjZV91c2VkX2ZpcnN0IHwgZGJsICAgICAgIHwgdXNlciBlcmFzZWQgZmlyc3QgY2hhcmFjdGVyIG9mIHJlc3BvbnNlICAgICAgIHwKfCBzdHVkeSAgICAgICAgICAgICAgICB8IGludCAgICAgICB8IHRyaWFsIHdhcyBhIHN0dWR5IHRyaWFsICAgICAgICAgICAgICAgICAgICAgICB8CnwgYW5zd2VyX2xhbmd1YWdlICAgICAgfCBjaHIgICAgICAgfCBsYW5ndWFnZSBvZiB0aGUgYW5zd2VyICAgICAgICAgICAgICAgICAgICAgICAgfAp8IHN1YnNlc3Npb24gICAgICAgICAgIHwgaW50ICAgICAgIHwgaWRlbnRpZmllcyBwYXJ0IHdpdGhpbiBsZWFybmluZyBzZXNzaW9uICAgICAgIHwKfCBib29rX2luZm9faWQgICAgICAgICB8IGNociAgICAgICB8IHVuaXF1ZSBpZGVudGlmaWVyIG9mIGJvb2sgaW5mb3JtYXRpb24gICAgICAgICB8CgoKIyMgYGJvb2tfaW5mb2AKCnwgQ29sdW1uICAgICAgICAgICAgICAgfCBUeXBlICAgICAgfCBFeHBsYW5hdGlvbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8LS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwKfCBib29rX2luZm9faWQgICAgICAgICB8IGNociAgICAgICB8IHVuaXF1ZSBpZGVudGlmaWVyIG9mIGJvb2sgaW5mb3JtYXRpb24gICAgICAgICB8CnwgbWV0aG9kX2dyb3VwICAgICAgICAgfCBjaHIgICAgICAgfCB5ZWFyIGFuZCBlZGl0aW9uICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IGJvb2tfdGl0bGUgICAgICAgICAgIHwgY2hyICAgICAgIHwgYm9vayB0aXRsZSAoaW5jbC4geWVhciwgbGV2ZWwsIGVkaXRpb24pICAgICAgIHwKfCBib29rX3R5cGUgICAgICAgICAgICB8IGNociAgICAgICB8IHR5cGUgb2YgYm9vayAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgY2hhcHRlciAgICAgICAgICAgICAgfCBjaHIgICAgICAgfCBjaGFwdGVyIG51bWJlciBhbmQgdGl0bGUgICAgICAgICAgICAgICAgICAgICAgfAoKClByZXZpZXcgZmlyc3QgMTAgcm93cwpgYGB7cn0KZGIgPC0gZGJfY29ubmVjdCgpCnJlc3BvbnNlc190b3AgPC0gZGJHZXRRdWVyeShkYiwgIlNFTEVDVCAqIEZST00gcmVzcG9uc2VzX25vZHVwbGljYXRlcyBMSU1JVCAxMCIpCnJlc3BvbnNlc190b3AKCmJvb2tfaW5mb190b3AgPC0gZGJHZXRRdWVyeShkYiwgIlNFTEVDVCAqIEZST00gYm9va19pbmZvIExJTUlUIDEwIikKYm9va19pbmZvX3RvcApkYl9kaXNjb25uZWN0KGRiKQpgYGAKCgoKIyBVc2FnZQoKR2V0IG51bWJlciBvZiB0cmlhbHMgYnkgbWV0aG9kLCBkYXksIGFuZCB1c2VyOgpgYGB7cn0KZGIgPC0gZGJfY29ubmVjdCgpCmNvdW50cyA8LSBkYkdldFF1ZXJ5KGRiLCJTRUxFQ1Qgci5tZXRob2QgQVMgJ21ldGhvZCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgREFURShyLmRhdGUgKyAzNjAwLCAndW5peGVwb2NoJykgQVMgJ2RveScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgci51c2VyX2lkIEFTICd1c2VyJywKICAgICAgICAgICAgICAgICAgICAgICAgICBDT1VOVCgqKSBBUyAndHJpYWxzJwogICAgICAgICAgICAgICAgICAgICAgICAgIEZST00gJ3Jlc3BvbnNlc19ub2R1cGxpY2F0ZXMnIHIKICAgICAgICAgICAgICAgICAgICAgICAgICBHUk9VUCBCWSByLm1ldGhvZCwKICAgICAgICAgICAgICAgICAgICAgICAgICBEQVRFKHIuZGF0ZSAgKyAzNjAwLCAndW5peGVwb2NoJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgci51c2VyX2lkCiAgICAgICAgICAgICAgICAgICAgICAgICIpCmRiX2Rpc2Nvbm5lY3QoZGIpCgpzZXREVChjb3VudHMpCmBgYAoKQWRkIGEgc2Nob29sIHllYXIgY29sdW1uIChjdXRvZmYgZGF0ZTogMSBBdWd1c3QpOgpgYGB7cn0KY291bnRzWywgZG95X3Bvc2l4IDo9IGFzLlBPU0lYY3QoZG95KV0KY291bnRzWywgc2Nob29sX3llYXIgOj0gaWZlbHNlKGRveV9wb3NpeCA8ICIyMDE5LTA4LTAxIiwgIjE4LzE5IiwgIjE5LzIwIildCmBgYAoKQWRkIG1vcmUgc2Vuc2libGUgY291cnNlIG5hbWVzOgpgYGB7cn0KY291bnRzWywgY291cnNlIDo9IGlmZWxzZShtZXRob2QgPT0gIkdyYW5kZXMgTGlnbmVzIiwgIkZyZW5jaCIsIGlmZWxzZShtZXRob2QgPT0gIlN0ZXBwaW5nIFN0b25lcyIsICJFbmdsaXNoIiwgIkdlcm1hbiIpKV0KYGBgCgoKIyMgVG90YWwgbnVtYmVyIG9mIHVuaXF1ZSB1c2VycyBieSBjb3Vyc2UgYW5kIHNjaG9vbCB5ZWFyCmBgYHtyfQpjb3VudHNbLCAuKHVuaXF1ZV91c2VycyA9IGxlbmd0aCh1bmlxdWUodXNlcikpKSwgYnkgPSAuKGNvdXJzZSwgc2Nob29sX3llYXIpXQpgYGAKClRoZXJlIHdhcyBzb21lIG92ZXJsYXAgYmV0d2VlbiBjb3Vyc2VzL3NjaG9vbCB5ZWFycywgc28gd2UgY2FuJ3Qgc2ltcGx5IGFkZCB0aGVzZSBudW1iZXJzLgoKQnkgY291cnNlOgpgYGB7cn0KY291bnRzWywgLih1bmlxdWVfdXNlcnMgPSBsZW5ndGgodW5pcXVlKHVzZXIpKSksIGJ5ID0gLihjb3Vyc2UpXQpgYGAKCgpCeSBzY2hvb2wgeWVhciBpbiB0aGUgRnJlbmNoIGFuZCBFbmdsaXNoIGNvdXJzZXM6CmBgYHtyfQpjb3VudHNbY291cnNlICVpbiUgYygiRnJlbmNoIiwgIkVuZ2xpc2giKSwgLih1bmlxdWVfdXNlcnMgPSBsZW5ndGgodW5pcXVlKHVzZXIpKSksIGJ5ID0gLihzY2hvb2xfeWVhcildCmBgYAoKQW5kIHRvdGFsIG51bWJlciBvZiB1bmlxdWUgdXNlcnMgaW4gdGhlIEZyZW5jaCBhbmQgRW5nbGlzaCBzYW1wbGUgYWNyb3NzIGJvdGggeWVhcnM6CmBgYHtyfQpjb3VudHNbY291cnNlICVpbiUgYygiRnJlbmNoIiwgIkVuZ2xpc2giKSwgLih1bmlxdWVfdXNlcnMgPSBsZW5ndGgodW5pcXVlKHVzZXIpKSldCmBgYAoKCiMjIFRvdGFsIG51bWJlciBvZiB0cmlhbHMgYnkgY291cnNlCmBgYHtyfQpjb3VudHNbLCAuKHRvdGFsX3RyaWFscyA9IHN1bSh0cmlhbHMpKSwgYnkgPSAuKGNvdXJzZSwgc2Nob29sX3llYXIpXQpgYGAKCiMjIE51bWJlciBvZiB0cmlhbHMgYnkgdXNlcgoKYGBge3J9CmNvdW50c1ssIHRyaWFsc191c2VyIDo9IHN1bSh0cmlhbHMpLCBieSA9IC4oY291cnNlLCB1c2VyKV0KZ2dwbG90KGNvdW50cywgYWVzKHggPSB0cmlhbHNfdXNlcikpICsKICBmYWNldF93cmFwKH5jb3Vyc2UsIG5jb2wgPSAxLCBzY2FsZXMgPSAiZnJlZV95IikgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMTAwKSArCiAgbGFicyh4ID0gIk51bWJlciBvZiB0cmlhbHMgYnkgdXNlciIsCiAgICAgICB5ID0gTlVMTCkgKwogIHRoZW1lX3BhcGVyCgpgYGAKCiMjIE51bWJlciBvZiB1bmlxdWUgZGF5cyBieSB1c2VyCmBgYHtyfQpnZ3Bsb3QoY291bnRzWywgLihOID0gbGVuZ3RoKHVuaXF1ZShkb3lfcG9zaXgpKSksIGJ5ID0gYygidXNlciIsICJjb3Vyc2UiKV0sIGFlcyh4ID0gTikpICsKICBmYWNldF9ncmlkKGNvdXJzZSB+IC4sIHNjYWxlcyA9ICJmcmVlX3kiKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxKSArCiAgbGFicyh4ID0gIkFhbnRhbCBkYWdlbiBtZXQgbGVlcmFjdGl2aXRlaXQiLAogICAgICAgeSA9ICJBYW50YWwgbGVlcmxpbmdlbiIpICsKICB0aGVtZV9wYXBlcgpgYGAKCgojIyBUb3RhbCBudW1iZXIgb2YgdHJpYWxzIGJ5IGRheQoKSW50ZXJwb2xhdGUgbWlzc2luZyBkYXlzOgpgYGB7cn0KZG95X3Bvc2l4IDwtIHNlcS5QT1NJWHQoZnJvbSA9IGNvdW50c1ssbWluKGRveV9wb3NpeCldLCB0byA9IGNvdW50c1ssbWF4KGRveV9wb3NpeCldLCBieSA9ICJEU1RkYXkiKQpjb3Vyc2UgPC0gY291bnRzWyx1bmlxdWUoY291cnNlKV0KZGF0ZXMgPC0gQ0ooZG95X3Bvc2l4LCBjb3Vyc2UpCmNvdW50cyA8LSBtZXJnZShjb3VudHMsIGRhdGVzLCBieSA9IGMoImRveV9wb3NpeCIsICJjb3Vyc2UiKSwgYWxsID0gVFJVRSkKYGBgCgpDb3VudCB0cmlhbHMgYnkgZGF5OgpgYGB7cn0KY291bnRzWywgdHJpYWxzX3RvdGFsIDo9IHN1bSh0cmlhbHMsIG5hLnJtID0gVFJVRSksIGJ5ID0gLihjb3Vyc2UsIGRveV9wb3NpeCldCmBgYAoKYGBge3J9CmNvdW50c19ieV9kYXkgPC0gY291bnRzWywgLih0cmlhbHNfdG90YWwgPSBzdW0odHJpYWxzLCBuYS5ybSA9IFRSVUUpKSwgYnkgPSAuKGNvdXJzZSwgZG95X3Bvc2l4KV0KYGBgCgoKYGBge3J9CmdncGxvdChjb3VudHNfYnlfZGF5W2NvdXJzZSAlaW4lIGMoIkVuZ2xpc2giLCAiRnJlbmNoIiksXSwKICAgICAgIGFlcyh4ID0gZG95X3Bvc2l4LCB5ID0gdHJpYWxzX3RvdGFsLCBjb2xvdXIgPSBjb3Vyc2UpKSArCiAgZ2VvbV9saW5lKCkgKwogIHNjYWxlX3hfZGF0ZXRpbWUoZGF0ZV9icmVha3MgPSAiMyBtb250aHMiLCBkYXRlX2xhYmVscyA9ICIlZSAlYiAlWSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gbnVtYmVyX2Zvcm1hdCkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIk51bWJlciBvZiB0cmlhbHMgcGVyIGRheSIsCiAgICAgICBjb2xvdXIgPSAiQ291cnNlIikgKwogIHRoZW1lX3BhcGVyCmBgYAoKCiMjIFRvdGFsIG51bWJlciBvZiB0cmlhbHMgYnkgd2VlawoKVXNlIGN1dC5EYXRlKCkgdG8gYmluIGRhdGVzIGJ5IHdlZWsuIEVhY2ggZGF5IGlzIGFzc2lnbmVkIHRoZSBkYXRlIG9mIHRoZSBtb3N0IHJlY2VudCBNb25kYXkuCmBgYHtyfQpjb3VudHNfYnlfZGF5WywgZG95X3Bvc2l4X3dlZWsgOj0gY3V0LlBPU0lYdChkb3lfcG9zaXgsICJ3ZWVrIildCmNvdW50c19ieV9kYXlbLCB0cmlhbHNfdG90YWxfd2VlayA6PSBzdW0odHJpYWxzX3RvdGFsLCBuYS5ybSA9IFRSVUUpLCBieSA9IC4oY291cnNlLCBkb3lfcG9zaXhfd2VlayldCmBgYAoKCmBgYHtyfQpnZ3Bsb3QoY291bnRzX2J5X2RheVtjb3Vyc2UgJWluJSBjKCJFbmdsaXNoIiwgIkZyZW5jaCIpLF0sCiAgICAgICAgICAgIGFlcyh4ID0gZG95X3Bvc2l4LCB5ID0gdHJpYWxzX3RvdGFsX3dlZWssIGNvbG91ciA9IGNvdXJzZSkpICsKICBnZW9tX2xpbmUoKSArCiAgc2NhbGVfeF9kYXRldGltZShkYXRlX2JyZWFrcyA9ICIzIG1vbnRocyIsIGRhdGVfbGFiZWxzID0gIiVlICViICVZIikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBudW1iZXJfZm9ybWF0KSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiTnVtYmVyIG9mIHRyaWFscyBwZXIgd2VlayIsCiAgICAgICBjb2xvdXIgPSAiQ291cnNlIikgKwogIHRoZW1lX3BhcGVyCmBgYAoKCk92ZXJsYXAgdGhlIHR3byBzY2hvb2wgeWVhcnM6CmBgYHtyfQpjb3VudHNfYnlfZGF5Wywgc2Nob29sX3llYXIgOj0gaWZlbHNlKGRveV9wb3NpeCA8ICIyMDE5LTA4LTAxIiwgIjE4LzE5IiwgIjE5LzIwIildCmNvdW50c19ieV9kYXlbc2Nob29sX3llYXIgPT0gIjE4LzE5IiwgZG95X3Bvc2l4X2FsaWduZWQgOj0gYXMuUE9TSVhjdChkb3lfcG9zaXggKyAzNjUqMjQqNjAqNjAsIG9yaWdpbiA9ICIxOTcwLTAxLTAxIildCmNvdW50c19ieV9kYXlbc2Nob29sX3llYXIgPT0gIjE5LzIwIiwgZG95X3Bvc2l4X2FsaWduZWQgOj0gZG95X3Bvc2l4XQpgYGAKCmBgYHtyfQpwX3RyaWFsX2hpc3QgPC0gZ2dwbG90KGNvdW50c19ieV9kYXlbY291cnNlICVpbiUgYygiRW5nbGlzaCIsICJGcmVuY2giKSxdLAogICAgICAgICAgICBhZXMoeCA9IGRveV9wb3NpeF9hbGlnbmVkLCB5bWluID0gMCwgeW1heCA9IHRyaWFsc190b3RhbF93ZWVrLCBncm91cCA9IHNjaG9vbF95ZWFyLCBjb2xvdXIgPSBzY2hvb2xfeWVhciwgZmlsbCA9IHNjaG9vbF95ZWFyKSkgKwogIGZhY2V0X3dyYXAofiBjb3Vyc2UsIG5jb2wgPSAxKSArCiAgZ2VvbV9yZWN0KHhtaW4gPSBkYXRlX3NjaG9vbHNfY2xvc2VkLCB4bWF4ID0gZGF0ZV9zY2hvb2xzX29wZW5lZCwgeW1pbiA9IC0yZTUsIHltYXggPSAyLjJlNiwgZmlsbCA9ICJncmV5OTIiLCBjb2xvdXIgPSAiZ3JleTUwIiwgbHR5ID0gMikgKwogIGdlb21fcmliYm9uKGFscGhhID0gLjIpICsKICBzY2FsZV94X2RhdGV0aW1lKGV4cGFuZCA9IGMoMCwgMCksIAogICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYXMuUE9TSVhjdChjKAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMS0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMS0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMy0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNS0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBhcy5QT1NJWGN0KGMoIjIwMTktMDktMDEgMDI6MDA6MDAgQ0VUIiwgIjIwMjAtMDctMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgZGF0ZV9sYWJlbHMgPSAiJWIiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGxpbWl0cyA9IGMoMCwgMmU2KSwgbGFiZWxzID0gbnVtYmVyX2Zvcm1hdCkgKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGVuZCA9IC41LCBkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9ICJUcmlhbHMgcGVyIHdlZWsiLAogICAgICAgY29sb3VyID0gIlNjaG9vbCB5ZWFyIiwKICAgICAgIGZpbGwgPSAiU2Nob29sIHllYXIiKSArCiAgdGhlbWVfcGFwZXIKCnBfdHJpYWxfaGlzdAoKZ2dzYXZlKCIuLi9vdXRwdXQvdHJpYWxfaGlzdC5wZGYiLCB3aWR0aCA9IDUsIGhlaWdodCA9IDMpCmdnc2F2ZSgiLi4vb3V0cHV0L3RyaWFsX2hpc3QuZXBzIiwgd2lkdGggPSA1LCBoZWlnaHQgPSAzKQpnZ3NhdmUoIi4uL291dHB1dC90cmlhbF9oaXN0LnBuZyIsIHdpZHRoID0gNSwgaGVpZ2h0ID0gMykKYGBgCgpNYWtlIGEgbGluZS1wbG90IHZlcnNpb24gb2YgdGhlIGhpc3RvZ3JhbS4KYGBge3J9CnBfdHJpYWxfaGlzdF9saW5lIDwtIGdncGxvdChjb3VudHNfYnlfZGF5W2NvdXJzZSAlaW4lIGMoIkVuZ2xpc2giLCAiRnJlbmNoIildLAogICAgICAgICAgICBhZXMoeCA9IGRveV9wb3NpeF9hbGlnbmVkLCB5ID0gdHJpYWxzX3RvdGFsX3dlZWssIGdyb3VwID0gc2Nob29sX3llYXIsIGNvbG91ciA9IHNjaG9vbF95ZWFyLCBmaWxsID0gc2Nob29sX3llYXIpKSArCiAgZmFjZXRfd3JhcCh+IGNvdXJzZSwgbmNvbCA9IDEpICsKICBnZW9tX3JlY3QoeG1pbiA9IGRhdGVfc2Nob29sc19jbG9zZWQsIHhtYXggPSBkYXRlX3NjaG9vbHNfb3BlbmVkLCB5bWluID0gLTJlNSwgeW1heCA9IDIuMmU2LCBmaWxsID0gImdyZXk5MiIsIGNvbG91ciA9ICJncmV5NTAiLCBsdHkgPSAyKSArCiAgZ2VvbV9saW5lKCkgKwogIHNjYWxlX3hfZGF0ZXRpbWUoZXhwYW5kID0gYygwLCAwKSwgCiAgICAgICAgICAgICAgICAgICBicmVha3MgPSBhcy5QT1NJWGN0KGMoCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTA5LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTExLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAxLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAzLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA1LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA3LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGFzLlBPU0lYY3QoYygiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLCAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBkYXRlX2xhYmVscyA9ICIlYiIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgbGltaXRzID0gYygwLCAyZTYpLCBsYWJlbHMgPSBudW1iZXJfZm9ybWF0KSArCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXNfZChlbmQgPSAuNSwgZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIlRyaWFscyBwZXIgd2VlayIsCiAgICAgICBjb2xvdXIgPSAiU2Nob29sIHllYXIiLAogICAgICAgZmlsbCA9ICJTY2hvb2wgeWVhciIpICsKICB0aGVtZV9wYXBlcgoKcF90cmlhbF9oaXN0X2xpbmUKCmdnc2F2ZSgiLi4vb3V0cHV0L3RyaWFsX2hpc3RfbGluZS5wZGYiLCB3aWR0aCA9IDUsIGhlaWdodCA9IDMpCmdnc2F2ZSgiLi4vb3V0cHV0L3RyaWFsX2hpc3RfbGluZS5lcHMiLCB3aWR0aCA9IDUsIGhlaWdodCA9IDMpCmdnc2F2ZSgiLi4vb3V0cHV0L3RyaWFsX2hpc3RfbGluZS5wbmciLCB3aWR0aCA9IDUsIGhlaWdodCA9IDMpCmBgYAoKQWxzbyBtYWtlIGEgZGlmZmVyZW5jZSBwbG90LgpgYGB7cn0KIyBJbiBvcmRlciBmb3IgdGhlIE1vbmRheXMgdG8gYWxpZ24sIG1vdmUgdGhlIDE4LzE5IGRhdGEgZm9yd2FyZCBieSAxIHllYXIgLSAxIGRheS4KY291bnRzX2J5X2RheVtzY2hvb2xfeWVhciA9PSAiMTgvMTkiLCBkb3lfcG9zaXhfYWxpZ25lZCA6PSBhcy5QT1NJWGN0KGRveV9wb3NpeCArIDM2NCoyNCo2MCo2MCwgb3JpZ2luID0gIjE5NzAtMDEtMDEiKV0KY291bnRzX2J5X2RheVtzY2hvb2xfeWVhciA9PSAiMTkvMjAiLCBkb3lfcG9zaXhfYWxpZ25lZCA6PSBkb3lfcG9zaXhdCmBgYAoKYGBge3J9CmNvdW50c19ieV9kYXlbLCB5ZWFyX2RpZmYgOj0gdHJpYWxzX3RvdGFsX3dlZWtbMl0gLSB0cmlhbHNfdG90YWxfd2Vla1sxXSwgYnkgPSAuKGNvdXJzZSwgZG95X3Bvc2l4X2FsaWduZWQpXQoKZ2dwbG90KGNvdW50c19ieV9kYXlbY291cnNlICVpbiUgYygiRW5nbGlzaCIsICJGcmVuY2giKV0sCiAgICAgICAgICAgIGFlcyh4ID0gZG95X3Bvc2l4X2FsaWduZWQsIHkgPSB5ZWFyX2RpZmYpKSArCiAgZmFjZXRfd3JhcCh+IGNvdXJzZSwgbmNvbCA9IDEpICsKICBnZW9tX3JlY3QoeG1pbiA9IGRhdGVfc2Nob29sc19jbG9zZWQsIHhtYXggPSBkYXRlX3NjaG9vbHNfb3BlbmVkLCB5bWluID0gLTFlNiwgeW1heCA9IDEuMWU2LCBmaWxsID0gImdyZXk5MiIsIGNvbG91ciA9ICJncmV5NTAiLCBsdHkgPSAyKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbHR5ID0gMykgKwogIGdlb21fbGluZSgpICsKICBzY2FsZV94X2RhdGV0aW1lKGV4cGFuZCA9IGMoMCwgMCksIAogICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYXMuUE9TSVhjdChjKAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMS0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMS0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMy0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNS0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBhcy5QT1NJWGN0KGMoIjIwMTktMDktMDEgMDI6MDA6MDAgQ0VUIiwgIjIwMjAtMDctMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgZGF0ZV9sYWJlbHMgPSAiJWIiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGxpbWl0cyA9IGMoLTNlNSwgMWU2KSwgbGFiZWxzID0gbnVtYmVyX2Zvcm1hdCkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIlRyaWFscyBwZXIgd2VlayIsCiAgICAgICBjb2xvdXIgPSAiU2Nob29sIHllYXIiLAogICAgICAgZmlsbCA9ICJTY2hvb2wgeWVhciIpICsKICB0aGVtZV9wYXBlcgpgYGAKCgojIyBOdW1iZXIgb2YgdHJpYWxzIGJ5IHVzZXIgYW5kIHdlZWsKYGBge3J9CmNvdW50c1ssIGRveV9wb3NpeF93ZWVrIDo9IGN1dC5QT1NJWHQoZG95X3Bvc2l4LCAid2VlayIpXQpjb3VudHNfYnlfdXNlcl9hbmRfd2VlayA8LSBjb3VudHNbLCAuKHRyaWFsc191c2VyID0gc3VtKHRyaWFscywgbmEucm0gPSBUUlVFKSksIGJ5ID0gLihjb3Vyc2UsIHNjaG9vbF95ZWFyLCB1c2VyLCBkb3lfcG9zaXhfd2VlayldCmBgYAoKU2F2ZSBmb3IgY2x1c3RlcmluZyBhbmFseXNpcwpgYGB7cn0Kc2F2ZVJEUyhuYS5vbWl0KGNvdW50c19ieV91c2VyX2FuZF93ZWVrW2NvdXJzZSAlaW4lIGMoIkVuZ2xpc2giLCAiRnJlbmNoIildKSwgIi4uL2RhdGEvdHJpYWxzX2J5X3VzZXJfYW5kX3dlZWsucmRzIikKYGBgCgoKIyMgVW5pcXVlIHVzZXJzIGJ5IGRheQoKYGBge3J9CnVzZXJzX2J5X2RheSA8LSBjb3VudHNbLCAuKHVuaXF1ZV91c2VycyA9IGxlbmd0aCh1bmlxdWUodXNlcikpKSwgYnkgPSAuKGNvdXJzZSwgZG95X3Bvc2l4KV0KYGBgCgpgYGB7cn0KcCA8LSBnZ3Bsb3QodXNlcnNfYnlfZGF5LCBhZXMoeCA9IGRveV9wb3NpeCwgeSA9IHVuaXF1ZV91c2VycywgY29sb3VyID0gY291cnNlKSkgKwogIGdlb21fbGluZSgpICsKICBzY2FsZV94X2RhdGV0aW1lKGRhdGVfYnJlYWtzID0gIjMgbW9udGhzIiwgZGF0ZV9sYWJlbHMgPSAiJWUgJWIgJVkiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IG51bWJlcl9mb3JtYXQpICsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9ICJOdW1iZXIgb2YgdXNlcnMgcGVyIGRheSIsCiAgICAgICBjb2xvdXIgPSAiQ291cnNlIikgKwogIHRoZW1lX3BhcGVyCgpwCmBgYAoKCiMjIFVuaXF1ZSB1c2VycyBieSB3ZWVrCgpVc2UgY3V0LkRhdGUoKSB0byBiaW4gZGF0ZXMgYnkgd2Vlay4gRWFjaCBkYXkgaXMgYXNzaWduZWQgdGhlIGRhdGUgb2YgdGhlIG1vc3QgcmVjZW50IE1vbmRheS4KYGBge3J9CnVzZXJzX2J5X2RheVssIGRveV9wb3NpeF93ZWVrIDo9IGN1dC5QT1NJWHQoZG95X3Bvc2l4LCAid2VlayIpXQpgYGAKCmBgYHtyfQp1c2Vyc19ieV93ZWVrIDwtIGNvdW50c1ssIC4odW5pcXVlX3VzZXJzX3dlZWsgPSBsZW5ndGgodW5pcXVlKHVzZXIpKSksIGJ5ID0gLihjb3Vyc2UsIGRveV9wb3NpeF93ZWVrKV0KdXNlcnNfYnlfd2VlayA8LSB1c2Vyc19ieV9kYXlbdXNlcnNfYnlfd2Vlaywgb24gPSAuKGNvdXJzZSwgZG95X3Bvc2l4X3dlZWspXQpgYGAKCmBgYHtyfQpwIDwtIGdncGxvdCh1c2Vyc19ieV93ZWVrLCBhZXMoeCA9IGRveV9wb3NpeCwgeW1pbiA9IDAsIHltYXggPSB1bmlxdWVfdXNlcnNfd2VlaywgZ3JvdXAgPSBjb3Vyc2UsIGNvbG91ciA9IGNvdXJzZSwgZmlsbCA9IGNvdXJzZSkpICsKICBmYWNldF93cmFwKH4gY291cnNlLCBuY29sID0gMSwgc2NhbGVzID0gImZyZWVfeSIpICsKICBnZW9tX3JpYmJvbihhbHBoYSA9IC4yKSArCiAgc2NhbGVfeF9kYXRldGltZShkYXRlX2JyZWFrcyA9ICIzIG1vbnRocyIsIGRhdGVfbGFiZWxzID0gIiVlICViICVZIikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6Om51bWJlcl9mb3JtYXQoYmlnLm1hcmsgPSAiLiIsIGRlY2ltYWwubWFyayA9ICIsIikpICsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9ICJBYW50YWwgZ2VicnVpa2VycyIsCiAgICAgICB0aXRsZSA9ICJBYW50YWwgdmVyc2NoaWxsZW5kZSBnZWJydWlrZXJzIHBlciB3ZWVrIiwKICAgICAgIGNhcHRpb24gPSAiTGV0IG9wOiBzY2hhYWwgdmVyc2NoaWx0IHR1c3NlbiBkZSBncmFmaWVrZW4iLAogICAgICAgY29sb3VyID0gIkxlc21ldGhvZGUiLAogICAgICAgZmlsbCA9ICJMZXNtZXRob2RlIikgKwogIGd1aWRlcyhjb2xvdXIgPSBGQUxTRSwgZmlsbCA9IEZBTFNFKSArCiAgdGhlbWVfcGFwZXIKCnAKYGBgCgpPdmVybGFwIHRoZSB0d28gc2Nob29sIHllYXJzOgpgYGB7cn0KdXNlcnNfYnlfd2Vla1ssIHNjaG9vbF95ZWFyIDo9IGlmZWxzZShkb3lfcG9zaXggPCAiMjAxOS0wOC0wMSIsICIxOC8xOSIsICIxOS8yMCIpXQp1c2Vyc19ieV93ZWVrW3NjaG9vbF95ZWFyID09ICIxOC8xOSIsIGRveV9wb3NpeF9hbGlnbmVkIDo9IGFzLlBPU0lYY3QoZG95X3Bvc2l4ICsgMzY1KjI0KjYwKjYwLCBvcmlnaW4gPSAiMTk3MC0wMS0wMSIpXQp1c2Vyc19ieV93ZWVrW3NjaG9vbF95ZWFyID09ICIxOS8yMCIsIGRveV9wb3NpeF9hbGlnbmVkIDo9IGRveV9wb3NpeF0KYGBgCgoKYGBge3J9CnBfdXNlcl9oaXN0IDwtIGdncGxvdCh1c2Vyc19ieV93ZWVrW2NvdXJzZSAlaW4lIGMoIkVuZ2xpc2giLCAiRnJlbmNoIiksXSwKICAgICAgICAgICAgYWVzKHggPSBkb3lfcG9zaXhfYWxpZ25lZCwgeW1pbiA9IDAsIHltYXggPSB1bmlxdWVfdXNlcnNfd2VlaywgZ3JvdXAgPSBzY2hvb2xfeWVhciwgY29sb3VyID0gc2Nob29sX3llYXIsIGZpbGwgPSBzY2hvb2xfeWVhcikpICsKICBmYWNldF93cmFwKH4gY291cnNlLCBuY29sID0gMSkgKwogIGdlb21fcmVjdCh4bWluID0gZGF0ZV9zY2hvb2xzX2Nsb3NlZCwgeG1heCA9IGRhdGVfc2Nob29sc19vcGVuZWQsIHltaW4gPSAtODAwLCB5bWF4ID0gODgwMCwgZmlsbCA9ICJncmV5OTIiLCBjb2xvdXIgPSAiZ3JleTUwIiwgbHR5ID0gMikgKwogIGdlb21fcmliYm9uKGFscGhhID0gLjIpICsKICBzY2FsZV94X2RhdGV0aW1lKGV4cGFuZCA9IGMoMCwgMCksIAogICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYXMuUE9TSVhjdChjKAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMS0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMS0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMy0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNS0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBhcy5QT1NJWGN0KGMoIjIwMTktMDktMDEgMDI6MDA6MDAgQ0VUIiwgIjIwMjAtMDctMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgZGF0ZV9sYWJlbHMgPSAiJWIiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGxpbWl0cyA9IGMoMCwgODAwMCksIGxhYmVscyA9IG51bWJlcl9mb3JtYXQpICsKICBzY2FsZV9jb2xvdXJfdmlyaWRpc19kKGVuZCA9IC41LCBkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChlbmQgPSAuNSwgZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiVW5pcXVlIHVzZXJzIHBlciB3ZWVrIiwKICAgICAgIGNvbG91ciA9ICJTY2hvb2wgeWVhciIsCiAgICAgICBmaWxsID0gIlNjaG9vbCB5ZWFyIikgKwogIHRoZW1lX3BhcGVyCgpwX3VzZXJfaGlzdAoKZ2dzYXZlKCIuLi9vdXRwdXQvdXNlcl9oaXN0LnBkZiIsIHdpZHRoID0gNSwgaGVpZ2h0ID0gMykKZ2dzYXZlKCIuLi9vdXRwdXQvdXNlcl9oaXN0LmVwcyIsIHdpZHRoID0gNSwgaGVpZ2h0ID0gMykKZ2dzYXZlKCIuLi9vdXRwdXQvdXNlcl9oaXN0LnBuZyIsIHdpZHRoID0gNSwgaGVpZ2h0ID0gMykKYGBgCgoKTWFrZSBhIGNvbWJpbmVkIHBsb3Qgb2YgdHJpYWwgYW5kIHVzZXIgY291bnRzIGZvciBpbiB0aGUgcGFwZXI6CmBgYHtyfQpwX2xlZ2VuZCA8LSBnZXRfbGVnZW5kKHBfdHJpYWxfaGlzdCkKCnBfdHJpYWxfaGlzdCA8LSBwX3RyaWFsX2hpc3QgKwogIGd1aWRlcyhjb2xvdXIgPSBGQUxTRSwgZmlsbCA9IEZBTFNFKQoKcF91c2VyX2hpc3QgPC0gcF91c2VyX2hpc3QgKwogIGd1aWRlcyhjb2xvdXIgPSBGQUxTRSwgZmlsbCA9IEZBTFNFKQpgYGAKCmBgYHtyfQpwbG90X2dyaWQocGxvdF9ncmlkKHBfdHJpYWxfaGlzdCwgcF91c2VyX2hpc3QsCiAgICAgICAgICBsYWJlbHMgPSBjKCJBIiwgIkIiKSwKICAgICAgICAgIGFsaWduID0gInYiLCBheGlzID0gInRibHIiKSwKICAgICAgICAgIHBfbGVnZW5kLAogICAgICAgICAgcmVsX3dpZHRocyA9IGMoMSwgLjIpKQoKZ2dzYXZlKCIuLi9vdXRwdXQvY29tYmlfaGlzdC5wZGYiLCB3aWR0aCA9IDksIGhlaWdodCA9IDMpCmdnc2F2ZSgiLi4vb3V0cHV0L2NvbWJpX2hpc3QuZXBzIiwgd2lkdGggPSA5LCBoZWlnaHQgPSAzKQpnZ3NhdmUoIi4uL291dHB1dC9jb21iaV9oaXN0LnBuZyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gMykKYGBgCgoKIyMgQWN0aXZpdHkgZHVyaW5nIHRoZSB3ZWVrCgpHZXQgbnVtYmVyIG9mIHRyaWFscyBieSBtZXRob2QsIGRheSwgaG91ciwgYW5kIHVzZXI6CmBgYHtyfQpkYiA8LSBkYl9jb25uZWN0KCkKY291bnRzX2J5X2hvdXIgPC0gZGJHZXRRdWVyeShkYiwiU0VMRUNUIHIubWV0aG9kIEFTICdtZXRob2QnLAogICAgICAgICAgICAgICAgICAgICAgICAgIERBVEUoci5kYXRlICsgMzYwMCwgJ3VuaXhlcG9jaCcpIEFTICdkb3knLAogICAgICAgICAgICAgICAgICAgICAgICAgIFNUUkZUSU1FKCclSCcsIHIuZGF0ZSArIDM2MDAsICd1bml4ZXBvY2gnKSBBUyAnaG91cicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgci51c2VyX2lkIEFTICd1c2VyJywKICAgICAgICAgICAgICAgICAgICAgICAgICBDT1VOVCgqKSBBUyAndHJpYWxzJwogICAgICAgICAgICAgICAgICAgICAgICAgIEZST00gJ3Jlc3BvbnNlc19ub2R1cGxpY2F0ZXMnIHIKICAgICAgICAgICAgICAgICAgICAgICAgICBHUk9VUCBCWSByLm1ldGhvZCwKICAgICAgICAgICAgICAgICAgICAgICAgICBEQVRFKHIuZGF0ZSArIDM2MDAsICd1bml4ZXBvY2gnKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBTVFJGVElNRSgnJUgnLCByLmRhdGUgKyAzNjAwLCAndW5peGVwb2NoJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgci51c2VyX2lkCiAgICAgICAgICAgICAgICAgICAgICAgICIpCmRiX2Rpc2Nvbm5lY3QoZGIpCgpzZXREVChjb3VudHNfYnlfaG91cikKYGBgCgpJbnRlcnBvbGF0ZSBtaXNzaW5nIGRheXMgYW5kIGhvdXJzOgpgYGB7cn0KY291bnRzX2J5X2hvdXJbLCBkb3lfcG9zaXggOj0gYXMuUE9TSVhjdChkb3kpXQpjb3VudHNfYnlfaG91clssIGhvdXIgOj0gYXMubnVtZXJpYyhob3VyKV0KZG95X3Bvc2l4IDwtIHNlcS5QT1NJWHQoZnJvbSA9IGNvdW50c19ieV9ob3VyWyxtaW4oZG95X3Bvc2l4KV0sIHRvID0gY291bnRzX2J5X2hvdXJbLG1heChkb3lfcG9zaXgpXSwgYnkgPSAiRFNUZGF5IikKbWV0aG9kIDwtIGNvdW50c19ieV9ob3VyWyx1bmlxdWUobWV0aG9kKV0KaG91ciA8LSAwOjIzCmRhdGVzX2FuZF9ob3VycyA8LSBDSihkb3lfcG9zaXgsIGhvdXIsIG1ldGhvZCkKY291bnRzX2J5X2hvdXIgPC0gbWVyZ2UoY291bnRzX2J5X2hvdXIsIGRhdGVzX2FuZF9ob3VycywgYnkgPSBjKCJkb3lfcG9zaXgiLCAiaG91ciIsICJtZXRob2QiKSwgYWxsID0gVFJVRSkKYGBgCgpBZGQgZGF5IG9mIHRoZSB3ZWVrOgpgYGB7cn0KY291bnRzX2J5X2hvdXJbLCB3ZWVrZGF5IDo9IHdlZWtkYXlzKGRveV9wb3NpeCldCmBgYAoKRGlzdGluZ3Vpc2ggYmV0d2VlbiBzY2hvb2wgeWVhcnM6CmBgYHtyfQpjb3VudHNfYnlfaG91clssIHNjaG9vbF95ZWFyIDo9IGlmZWxzZShkb3lfcG9zaXggPCAiMjAxOS0wOC0wMSIsICIxOC8xOSIsICIxOS8yMCIpXQpgYGAKCkFkZCBxdWFydGVyOgpgYGB7cn0KY291bnRzX2J5X2hvdXJbLCBxdWFydGVyIDo9IHBhc3RlMCh5ZWFyKGRveV9wb3NpeCksICJRIiwgcXVhcnRlcihkb3lfcG9zaXgpKV0KYGBgCgoKQWRkIGV4YWN0IHNjaG9vbCBjbG9zdXJlIHBlcmlvZCBpbiBib3RoIHNjaG9vbCB5ZWFyczoKYGBge3J9CmNvdW50c19ieV9ob3VyW3NjaG9vbF95ZWFyID09ICIxOC8xOSIsIGRveV9wb3NpeF9hbGlnbmVkIDo9IGFzLlBPU0lYY3QoZG95X3Bvc2l4ICsgMzY1KjI0KjYwKjYwLCBvcmlnaW4gPSAiMTk3MC0wMS0wMSIpXQpjb3VudHNfYnlfaG91cltzY2hvb2xfeWVhciA9PSAiMTkvMjAiLCBkb3lfcG9zaXhfYWxpZ25lZCA6PSBkb3lfcG9zaXhdCgpjb3VudHNfYnlfaG91clssIHNjaG9vbHNfY2xvc2VkIDo9IGRveV9wb3NpeF9hbGlnbmVkID49IGRhdGVfc2Nob29sc19jbG9zZWQgJiBkb3lfcG9zaXhfYWxpZ25lZCA8IGRhdGVfc2Nob29sc19vcGVuZWRdCmBgYAoKCkFkZCBtb3JlIHNlbnNpYmxlIGNvdXJzZSBuYW1lczoKYGBge3J9CmNvdW50c19ieV9ob3VyWywgY291cnNlIDo9IGlmZWxzZShtZXRob2QgPT0gIkdyYW5kZXMgTGlnbmVzIiwgIkZyZW5jaCIsIGlmZWxzZShtZXRob2QgPT0gIlN0ZXBwaW5nIFN0b25lcyIsICJFbmdsaXNoIiwgIkdlcm1hbiIpKV0KYGBgCgoKU3VtIHRyaWFscyBieSBzY2hvb2wgeWVhciwgd2Vla2RheSBhbmQgaG91cjoKYGBge3J9CmNvdW50c19ieV9ob3VyWywgdHJpYWxzX3NjaG9vbHllYXIgOj0gc3VtKHRyaWFscywgbmEucm0gPSBUUlVFKSwgYnkgPSAuKGNvdXJzZSwgc2Nob29sX3llYXIsIHdlZWtkYXksIGhvdXIpXQpgYGAKCkFsc28gc3VtIHRyaWFscyBieSBxdWFydGVyLCB3ZWVrZGF5IGFuZCBob3VyOgpgYGB7cn0KY291bnRzX2J5X2hvdXJbLCB0cmlhbHNfcXVhcnRlciA6PSBzdW0odHJpYWxzLCBuYS5ybSA9IFRSVUUpLCBieSA9IC4oY291cnNlLCBxdWFydGVyLCB3ZWVrZGF5LCBob3VyKV0KYGBgCgpBbmQgc3VtIHRyaWFscyB3aXRoaW4gdGhlIGNsb3N1cmUgcGVyaW9kIGJ5IHdlZWtkYXkgYW5kIGhvdXI6CmBgYHtyfQpjb3VudHNfYnlfaG91cltzY2hvb2xzX2Nsb3NlZCA9PSBUUlVFLCB0cmlhbHNfY2xvc2VkIDo9IHN1bSh0cmlhbHMsIG5hLnJtID0gVFJVRSksIGJ5ID0gLihjb3Vyc2UsIHNjaG9vbF95ZWFyLCB3ZWVrZGF5LCBob3VyKV0KYGBgCgoKYGBge3J9CnRyaWFsc19ieV93ZGF5X2hvdXIgPC0gdW5pcXVlKGNvdW50c19ieV9ob3VyLCBieSA9IGMoImNvdXJzZSIsICJzY2hvb2xfeWVhciIsICJxdWFydGVyIiwgInNjaG9vbHNfY2xvc2VkIiwgIndlZWtkYXkiLCAiaG91ciIpKQoKdHJpYWxzX2J5X3dkYXlfaG91clssIHRyaWFsc19ub3JtYWxpc2VkX3NjaG9vbHllYXIgOj0gdHJpYWxzX3NjaG9vbHllYXIgLyBzdW0odHJpYWxzX3NjaG9vbHllYXIpLCBieSA9IC4oY291cnNlKV0KdHJpYWxzX2J5X3dkYXlfaG91clssIHRyaWFsc19ub3JtYWxpc2VkX3F1YXJ0ZXIgOj0gdHJpYWxzX3F1YXJ0ZXIgLyBzdW0odHJpYWxzX3F1YXJ0ZXIpLCBieSA9IC4oY291cnNlKV0KCnRyaWFsc19ieV93ZGF5X2hvdXJbLCB3ZWVrZGF5IDo9IG9yZGVyZWQod2Vla2RheSwgbGV2ZWxzID0gYygiTW9uZGF5IiwgIlR1ZXNkYXkiLCAiV2VkbmVzZGF5IiwgIlRodXJzZGF5IiwgIkZyaWRheSIsICJTYXR1cmRheSIsICJTdW5kYXkiKSldCiMgdHJpYWxzX2J5X3dkYXlfaG91clssIHdlZWtkYXkgOj0gb3JkZXJlZCh3ZWVrZGF5LCBsZXZlbHMgPSBjKCJtYWFuZGFnIiwgImRpbnNkYWciLCAid29lbnNkYWciLCAiZG9uZGVyZGFnIiwgInZyaWpkYWciLCAiemF0ZXJkYWciLCAiem9uZGFnIikpXQpgYGAKCgpQbG90IGhlYXRtYXAgZm9yIHRoZSB3aG9sZSBzY2hvb2wgeWVhcjoKYGBge3J9CmdncGxvdCh0cmlhbHNfYnlfd2RheV9ob3VyW2NvdXJzZSAlaW4lIGMoIkVuZ2xpc2giLCAiRnJlbmNoIildLAogICAgICAgYWVzKHggPSBob3VyLCB5ID0gcmVvcmRlcih3ZWVrZGF5LCBkcGx5cjo6ZGVzYyh3ZWVrZGF5KSksIGZpbGwgPSB0cmlhbHNfbm9ybWFsaXNlZF9zY2hvb2x5ZWFyKSkgKyAKICBmYWNldF9ncmlkKHNjaG9vbF95ZWFyIH4gY291cnNlKSArCiAgZ2VvbV90aWxlKGNvbG91ciA9ICJ3aGl0ZSIsIHNpemUgPSAwLjI1KSArCiAgbGFicyh4ID0gIlRpbWUgb2YgZGF5IChob3VyKSIsCiAgICAgICB5ID0gTlVMTCkgKwogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGJyZWFrcyA9IHNlcSgwLCAyNCwgMykpICsKICBzY2FsZV95X2Rpc2NyZXRlKGV4cGFuZCA9IGMoMCwwKSkgKyAKICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhvcHRpb24gPSAiQSIsIGRpcmVjdGlvbiA9IC0xKSArCiAgY29vcmRfZml4ZWQoKSArCiAgZ3VpZGVzKGZpbGwgPSBGQUxTRSkgKwogIHRoZW1lX3BhcGVyCmBgYAoKUGxvdCBoZWF0bWFwIHBlciBxdWFydGVyOgpgYGB7ciwgZmlnLndpZHRoID0gMTIsIGZpZy5oZWlnaHQgPSAxNn0KZ2dwbG90KHRyaWFsc19ieV93ZGF5X2hvdXIsIGFlcyh4ID0gaG91ciwgeSA9IHJlb3JkZXIod2Vla2RheSwgZHBseXI6OmRlc2Mod2Vla2RheSkpLCBmaWxsID0gdHJpYWxzX25vcm1hbGlzZWRfcXVhcnRlcikpICsgCiAgZmFjZXRfZ3JpZChxdWFydGVyIH4gbWV0aG9kKSArCiAgZ2VvbV90aWxlKGNvbG91ciA9ICJ3aGl0ZSIsIHNpemUgPSAwLjI1KSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSBOVUxMLAogICAgICAgdGl0bGUgPSAiQWN0aXZpdGVpdCBwZXIgdXVyIGdlZHVyZW5kZSBkZSB3ZWVrIiwKICAgICAgIGNhcHRpb24gPSAiQWFudGFsIHRyaWFscyBwZXIgd2Vla2RhZyBlbiB1dXIgaW4gZWxrIGt3YXJ0YWFsLCBnZW5vcm1hbGlzZWVyZCBwZXIgbWV0aG9kZS4iKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSwgYnJlYWtzID0gc2VxKDAsIDI0LCAzKSkgKwogIHNjYWxlX3lfZGlzY3JldGUoZXhwYW5kID0gYygwLDApKSArIAogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJBIiwgZGlyZWN0aW9uID0gLTEpICsKICBjb29yZF9maXhlZCgpICsKICBndWlkZXMoZmlsbCA9IEZBTFNFKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpCmBgYAoKUGxvdCBoZWF0bWFwIGZvciB0aGUgcGVyaW9kIGluIHdoaWNoIHNjaG9vbHMgd2VyZSBjbG9zZWQ6CmBgYHtyfQp0cmlhbHNfY2xvc2VkIDwtIHVuaXF1ZSh0cmlhbHNfYnlfd2RheV9ob3VyW3NjaG9vbHNfY2xvc2VkID09IFRSVUUsIC4oY291cnNlLCBzY2hvb2xfeWVhciwgd2Vla2RheSwgaG91ciwgdHJpYWxzX2Nsb3NlZCldKQoKdHJpYWxzX2Nsb3NlZFssIHRyaWFsc19ub3JtYWxpc2VkX2Nsb3NlZCA6PSB0cmlhbHNfY2xvc2VkIC8gc3VtKHRyaWFsc19jbG9zZWQpLCBieSA9IC4oY291cnNlLCBzY2hvb2xfeWVhcildCmBgYAoKYGBge3J9CnRyaWFsc19jbG9zZWRfZGlmZiA8LSB0cmlhbHNfY2xvc2VkWywgLihzY2hvb2xfeWVhciA9ICJDaGFuZ2UiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJpYWxzX2Nsb3NlZCA9IHRyaWFsc19jbG9zZWRbc2Nob29sX3llYXIgPT0gIjE5LzIwIl0gLSB0cmlhbHNfY2xvc2VkW3NjaG9vbF95ZWFyID09ICIxOC8xOSJdLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJpYWxzX25vcm1hbGlzZWRfY2xvc2VkID0gdHJpYWxzX25vcm1hbGlzZWRfY2xvc2VkW3NjaG9vbF95ZWFyID09ICIxOS8yMCJdIC0gdHJpYWxzX25vcm1hbGlzZWRfY2xvc2VkW3NjaG9vbF95ZWFyID09ICIxOC8xOSJdKSwgYnkgPSAuKGNvdXJzZSwgd2Vla2RheSwgaG91cildCmBgYAoKCmBgYHtyfQpwX2hlYXRtYXAgPC0gZ2dwbG90KHRyaWFsc19jbG9zZWRbY291cnNlICVpbiUgYygiRW5nbGlzaCIsICJGcmVuY2giKSxdLAogICAgICAgYWVzKHggPSBob3VyLCB5ID0gcmVvcmRlcih3ZWVrZGF5LCBkcGx5cjo6ZGVzYyh3ZWVrZGF5KSksIGZpbGwgPSB0cmlhbHNfbm9ybWFsaXNlZF9jbG9zZWQpKSArIAogIGZhY2V0X2dyaWQoc2Nob29sX3llYXIgfiBjb3Vyc2UpICsKICBnZW9tX3RpbGUoY29sb3VyID0gIndoaXRlIiwgc2l6ZSA9IDAuMjUpICsKICBsYWJzKHggPSAiVGltZSBvZiBkYXkgKGhvdXIpIiwKICAgICAgIHkgPSBOVUxMLAogICAgICAgZmlsbCA9IE5VTEwpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApLCBicmVha3MgPSBzZXEoMCwgMjQsIDMpKSArCiAgc2NhbGVfeV9kaXNjcmV0ZShleHBhbmQgPSBjKDAsMCkpICsgCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Mob3B0aW9uID0gIkEiLCBkaXJlY3Rpb24gPSAtMSkgKwogIGNvb3JkX2ZpeGVkKCkgKwogIHRoZW1lX3BhcGVyCgoKcF9oZWF0bWFwCmBgYAoKCk1ha2UgYSBwbG90IG9mIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIHR3byBzY2hvb2wgeWVhcnMgZHVyaW5nIHRoZSBzY2hvb2wgY2xvc3VyZSBwZXJpb2Q6CmBgYHtyfQpwX2hlYXRtYXBfZGlmZiA8LSBnZ3Bsb3QodHJpYWxzX2Nsb3NlZF9kaWZmW2NvdXJzZSAlaW4lIGMoIkVuZ2xpc2giLCAiRnJlbmNoIiksXSwKICAgICAgIGFlcyh4ID0gaG91ciwgeSA9IHJlb3JkZXIod2Vla2RheSwgZHBseXI6OmRlc2Mod2Vla2RheSkpLCBmaWxsID0gdHJpYWxzX25vcm1hbGlzZWRfY2xvc2VkKSkgKyAKICBmYWNldF9ncmlkKHNjaG9vbF95ZWFyIH4gY291cnNlKSArCiAgZ2VvbV90aWxlKGNvbG91ciA9ICJ3aGl0ZSIsIHNpemUgPSAwLjI1KSArCiAgbGFicyh4ID0gIlRpbWUgb2YgZGF5IChob3VyKSIsCiAgICAgICB5ID0gTlVMTCwKICAgICAgIGZpbGwgPSBOVUxMKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSwgYnJlYWtzID0gc2VxKDAsIDI0LCAzKSkgKwogIHNjYWxlX3lfZGlzY3JldGUoZXhwYW5kID0gYygwLDApKSArIAogIHNjYWxlX2ZpbGxfZGlzdGlsbGVyKHR5cGUgPSAiZGl2IiwgcGFsZXR0ZSA9ICJSZEJ1IiwgZGlyZWN0aW9uID0gLTEsIGxpbWl0cyA9IGMoLTEsIDEpICogbWF4KGFicyh0cmlhbHNfY2xvc2VkX2RpZmZbY291cnNlICVpbiUgYygiRW5nbGlzaCIsICJGcmVuY2giKSxdJHRyaWFsc19ub3JtYWxpc2VkX2Nsb3NlZCkpKSArCiAgY29vcmRfZml4ZWQoKSArCiAgdGhlbWVfcGFwZXIKCnBfaGVhdG1hcF9kaWZmCmBgYAoKCk1ha2UgYSBjb21iaW5lZCBwbG90IGZvciBpbiB0aGUgcGFwZXI6CmBgYHtyfQpwX2hlYXRtYXBfbGVnZW5kIDwtIGdldF9sZWdlbmQocF9oZWF0bWFwKQpwX2hlYXRtYXBfZGlmZl9sZWdlbmQgPC0gZ2V0X2xlZ2VuZChwX2hlYXRtYXBfZGlmZikKCnBfaGVhdG1hcCA8LSBwX2hlYXRtYXAgKyBndWlkZXMoZmlsbCA9IEZBTFNFKQpwX2hlYXRtYXBfZGlmZiA8LSBwX2hlYXRtYXBfZGlmZiArIGd1aWRlcyhmaWxsID0gRkFMU0UpCmBgYAoKYGBge3J9CnBsb3RfZ3JpZCgKICBwbG90X2dyaWQocF9oZWF0bWFwLCBwX2hlYXRtYXBfZGlmZiwKICAgICAgICAgIG5jb2wgPSAxLAogICAgICAgICAgbGFiZWxzID0gYygiQSIsICJCIiksCiAgICAgICAgICByZWxfaGVpZ2h0cyA9IGMoMSwgLjY1NSkKICAgICAgICAgICksCiAgcGxvdF9ncmlkKHBfaGVhdG1hcF9sZWdlbmQsIHBfaGVhdG1hcF9kaWZmX2xlZ2VuZCwKICAgICAgICAgICAgbmNvbCA9IDEsCiAgICAgICAgICAgIGFsaWduID0gInZoIiwgYXhpcyA9ICJscnRiIiksCiAgbmNvbCA9IDIsCiAgcmVsX3dpZHRocyA9IGMoMSwgLjE1KSkKCmdnc2F2ZSgiLi4vb3V0cHV0L2NvbWJpX2hlYXRtYXAucGRmIiwgd2lkdGggPSA5LCBoZWlnaHQgPSA0LjUpCmdnc2F2ZSgiLi4vb3V0cHV0L2NvbWJpX2hlYXRtYXAuZXBzIiwgd2lkdGggPSA5LCBoZWlnaHQgPSA0LjUpCmdnc2F2ZSgiLi4vb3V0cHV0L2NvbWJpX2hlYXRtYXAucG5nIiwgd2lkdGggPSA5LCBoZWlnaHQgPSA0LjUpCmBgYAoKCiMjIEFjdGl2aXR5IHN0cmF0aWZpZWQgYnkgeWVhciBhbmQgbGV2ZWwKCmBgYHtyfQpkYiA8LSBkYl9jb25uZWN0KCkKY291bnRzX3N0cmF0IDwtIGRiR2V0UXVlcnkoZGIsIlNFTEVDVCByLm1ldGhvZCBBUyAnbWV0aG9kJywKICAgICAgICAgICAgICAgICAgICAgICAgICByLmJvb2tfaW5mb19pZCBhcyAnYm9va19pbmZvX2lkJywKICAgICAgICAgICAgICAgICAgICAgICAgICBEQVRFKHIuZGF0ZSArIDM2MDAsICd1bml4ZXBvY2gnKSBBUyAnZG95JywKICAgICAgICAgICAgICAgICAgICAgICAgICByLnVzZXJfaWQgQVMgJ3VzZXInLAogICAgICAgICAgICAgICAgICAgICAgICAgIENPVU5UKCopIEFTICd0cmlhbHMnCiAgICAgICAgICAgICAgICAgICAgICAgICAgRlJPTSAncmVzcG9uc2VzX25vZHVwbGljYXRlcycgcgogICAgICAgICAgICAgICAgICAgICAgICAgIEdST1VQIEJZIHIubWV0aG9kLAogICAgICAgICAgICAgICAgICAgICAgICAgIHIuYm9va19pbmZvX2lkLAogICAgICAgICAgICAgICAgICAgICAgICAgIERBVEUoci5kYXRlICsgMzYwMCwgJ3VuaXhlcG9jaCcpLAogICAgICAgICAgICAgICAgICAgICAgICAgIHIudXNlcl9pZAogICAgICAgICAgICAgICAgICAgICAgICAiKQpkYl9kaXNjb25uZWN0KGRiKQoKc2V0RFQoY291bnRzX3N0cmF0KQpgYGAKCmBgYHtyfQpkYiA8LSBkYl9jb25uZWN0KCkKYm9va19pbmZvIDwtIGRiR2V0UXVlcnkoZGIsICJTRUxFQ1QgKiBGUk9NICdib29rX2luZm8nIikKZGJfZGlzY29ubmVjdChkYikKCnNldERUKGJvb2tfaW5mbykKYGBgCgpBZGQgYm9vayBpbmZvcm1hdGlvbjoKYGBge3J9CmNvdW50c19zdHJhdFtib29rX2luZm8sIG9uID0gImJvb2tfaW5mb19pZCIsIGMoImJvb2tfdGl0bGUiLCAibWV0aG9kX2dyb3VwIikgOj0gLihpLmJvb2tfdGl0bGUsIGkubWV0aG9kX2dyb3VwKV0KYGBgCgpBZGQgYSBzY2hvb2wgeWVhciBjb2x1bW4gKGN1dG9mZiBkYXRlOiAxIEF1Z3VzdCk6CmBgYHtyfQpjb3VudHNfc3RyYXRbLCBkb3lfcG9zaXggOj0gYXMuUE9TSVhjdChkb3kpXQpjb3VudHNfc3RyYXRbLCBzY2hvb2xfeWVhciA6PSBpZmVsc2UoZG95X3Bvc2l4IDwgIjIwMTktMDgtMDEiLCAiMTgvMTkiLCAiMTkvMjAiKV0KYGBgCgpBZGQgc2Vuc2libGUgY291cnNlIG5hbWVzOgpgYGB7cn0KY291bnRzX3N0cmF0WywgY291cnNlIDo9IGlmZWxzZShtZXRob2QgPT0gIkdyYW5kZXMgTGlnbmVzIiwgIkZyZW5jaCIsIGlmZWxzZShtZXRob2QgPT0gIlN0ZXBwaW5nIFN0b25lcyIsICJFbmdsaXNoIiwgIkdlcm1hbiIpKV0KYGBgCgpDb3VudCB0cmlhbHMgYnkgZGF5OgpgYGB7cn0KY291bnRzX3N0cmF0X2J5X2RheSA8LSBjb3VudHNfc3RyYXRbLCAuKHRyaWFsc190b3RhbCA9IHN1bSh0cmlhbHMsIG5hLnJtID0gVFJVRSkpLCBieSA9IC4oc2Nob29sX3llYXIsIGNvdXJzZSwgbWV0aG9kX2dyb3VwLCBib29rX3RpdGxlLCBkb3lfcG9zaXgpXQpzZXRvcmRlcihjb3VudHNfc3RyYXRfYnlfZGF5LCBzY2hvb2xfeWVhciwgY291cnNlLCBtZXRob2RfZ3JvdXAsIGJvb2tfdGl0bGUsIGRveV9wb3NpeCkKYGBgCgpTaW1wbGlmeSBsZXZlbCBuYW1lczoKYGBge3J9CiMgS2VlcCBhbGwgZGlzdGluY3Rpb25zCmNvdW50c19zdHJhdF9ieV9kYXlbLCBib29rX3RpdGxlX3NpbXBsZSA6PSBzdHJpbmdyOjpzdHJfc3ViKGJvb2tfdGl0bGUsIDMsIC0xMCldCmNvdW50c19zdHJhdF9ieV9kYXlbLCBib29rX3RpdGxlX3NpbXBsZSA6PSBmYWN0b3IoYm9va190aXRsZV9zaW1wbGUsIGxldmVscyA9IGMoInZtYm8gYi9sd29vIiwgInZtYm8gYiIsICJ2bWJvIGJrIiwgInZtYm8gayIsICJ2bWJvIGtndCIsICJ2bWJvLWd0IiwgInZtYm8gZ3QiLCAidm1iby1ndC9oYXZvIiwgInZtYm8gKHQpaHYiLCAiaGF2byIsICJoYXZvIHZ3byIsICJ2d28iKSldCgojIFNpbXBsaWZ5IHRvIHRocmVlIGxldmVscwpjb3VudHNfc3RyYXRfYnlfZGF5WywgbGV2ZWwgOj0gZHBseXI6OmNhc2Vfd2hlbigKICBncmVwbCggImh2IiwgYm9va190aXRsZSkgfiAiR2VuZXJhbCBzZWNvbmRhcnlcbihoYXZvKSIsCiAgZ3JlcGwoInZtYm8iLCBib29rX3RpdGxlKSB+ICJQcmUtdm9jYXRpb25hbFxuKHZtYm8pIiwKICBncmVwbCgiaGF2byIsIGJvb2tfdGl0bGUpIH4gIkdlbmVyYWwgc2Vjb25kYXJ5XG4oaGF2bykiLAogIGdyZXBsKCJ2d28iLCBib29rX3RpdGxlKSB+ICJQcmUtdW5pdmVyc2l0eVxuKHZ3bykiLAogIFRSVUUgfiAiT3RoZXIiKV0KY291bnRzX3N0cmF0X2J5X2RheVssIGxldmVsIDo9IGZhY3RvcihsZXZlbCwgbGV2ZWxzID0gYygiT3RoZXIiLCAiUHJlLXZvY2F0aW9uYWxcbih2bWJvKSIsICJHZW5lcmFsIHNlY29uZGFyeVxuKGhhdm8pIiwgIlByZS11bml2ZXJzaXR5XG4odndvKSIpKV0KYGBgCgpTaW1wbGlmeSB5ZWFyIG5hbWVzOgpgYGB7cn0KY291bnRzX3N0cmF0X2J5X2RheVssIHllYXIgOj0gZHBseXI6OmNhc2Vfd2hlbigKICBtZXRob2RfZ3JvdXAgPT0gIkxlZXJqYWFyIDEgKDVlIEVkLikiIH4gIlllYXIgMSIsCiAgbWV0aG9kX2dyb3VwID09ICJMZWVyamFhciAyICg1ZSBFZC4pIiB+ICJZZWFyIDIiLAogIG1ldGhvZF9ncm91cCA9PSAiTGVlcmphYXIgMyAoNWUgRWQuKSIgfiAiWWVhciAzIiwKICBtZXRob2RfZ3JvdXAgPT0gIkxlZXJqYWFyIDMvNCAoNWUgRWQuKSIgfiAiWWVhciAzLzQiLAogIG1ldGhvZF9ncm91cCA9PSAiTGVlcmphYXIgNCAoNWUgRWQuKSIgfiAiWWVhciA0IiwKICBtZXRob2RfZ3JvdXAgPT0gIlR3ZWVkZSBGYXNlICg2ZSBFZC4pIiB+ICJUd2VlZGUgRmFzZSIsCiAgVFJVRSB+ICJPdGhlciIpXQpgYGAKCgpBbGlnbiBzY2hvb2wgeWVhcnM6CmBgYHtyfQpjb3VudHNfc3RyYXRfYnlfZGF5W3NjaG9vbF95ZWFyID09ICIxOC8xOSIsIGRveV9wb3NpeF9hbGlnbmVkIDo9IGFzLlBPU0lYY3QoZG95X3Bvc2l4ICsgMzY1KjI0KjYwKjYwLCBvcmlnaW4gPSAiMTk3MC0wMS0wMSIpXQpjb3VudHNfc3RyYXRfYnlfZGF5W3NjaG9vbF95ZWFyID09ICIxOS8yMCIsIGRveV9wb3NpeF9hbGlnbmVkIDo9IGRveV9wb3NpeF0KYGBgCgpVc2UgY3V0LkRhdGUoKSB0byBiaW4gZGF0ZXMgYnkgd2Vlay4gRWFjaCBkYXkgaXMgYXNzaWduZWQgdGhlIGRhdGUgb2YgdGhlIG1vc3QgcmVjZW50IE1vbmRheS4KYGBge3J9CmNvdW50c19zdHJhdF9ieV9kYXlbLCBkb3lfcG9zaXhfYWxpZ25lZF93ZWVrIDo9IGN1dC5QT1NJWHQoZG95X3Bvc2l4X2FsaWduZWQsICJ3ZWVrIildCmNvdW50c19zdHJhdF9ieV9kYXlbLCB0cmlhbHNfdG90YWxfd2VlayA6PSBzdW0odHJpYWxzX3RvdGFsLCBuYS5ybSA9IFRSVUUpLCBieSA9IC4oc2Nob29sX3llYXIsIGNvdXJzZSwgbWV0aG9kX2dyb3VwLCBib29rX3RpdGxlX3NpbXBsZSwgZG95X3Bvc2l4X2FsaWduZWRfd2VlayldCgpjb3VudHNfc3RyYXRfYnlfZGF5WywgdHJpYWxzX3RvdGFsX3dlZWtfbGV2ZWwgOj0gc3VtKHRyaWFsc190b3RhbCksIGJ5ID0gLihzY2hvb2xfeWVhciwgY291cnNlLCBtZXRob2RfZ3JvdXAsIGxldmVsLCBkb3lfcG9zaXhfYWxpZ25lZF93ZWVrKV0KYGBgCgoKU3VtbWFyaXNlIGluY3JlYXNlIGR1cmluZyBsb2NrZG93bjoKYGBge3J9CmNvdW50c19zdHJhdF9pbmNyZWFzZSA8LSBjb3VudHNfc3RyYXRfYnlfZGF5W2JldHdlZW4oZG95X3Bvc2l4X2FsaWduZWQsIGRhdGVfc2Nob29sc19jbG9zZWQsIGRhdGVfc2Nob29sc19vcGVuZWQpLCAuKHRyaWFsc19sb2NrZG93biA9IHN1bSh0cmlhbHNfdG90YWwpKSwgYnkgPSAuKGNvdXJzZSwgYm9va190aXRsZV9zaW1wbGUsIG1ldGhvZF9ncm91cCwgeWVhciwgc2Nob29sX3llYXIpXQpjb3VudHNfc3RyYXRfaW5jcmVhc2VbLCBpbmNyZWFzZSA6PSB0cmlhbHNfbG9ja2Rvd25bMl0vdHJpYWxzX2xvY2tkb3duWzFdLCBieSA9IC4oY291cnNlLCBib29rX3RpdGxlX3NpbXBsZSwgbWV0aG9kX2dyb3VwLCB5ZWFyKV0KY291bnRzX3N0cmF0X2luY3JlYXNlWywgaW5jcmVhc2VfcGN0IDo9IHBhc3RlMCgiQ2hhbmdlOlxuIiwgc2NhbGVzOjpwZXJjZW50KGluY3JlYXNlLCBhY2N1cmFjeSA9IDIpKV0KCmNvdW50c19zdHJhdF9pbmNyZWFzZV9sZXZlbCA8LSBjb3VudHNfc3RyYXRfYnlfZGF5W2JldHdlZW4oZG95X3Bvc2l4X2FsaWduZWQsIGRhdGVfc2Nob29sc19jbG9zZWQsIGRhdGVfc2Nob29sc19vcGVuZWQpLCAuKHRyaWFsc19sb2NrZG93biA9IHN1bSh0cmlhbHNfdG90YWwpKSwgYnkgPSAuKGNvdXJzZSwgbGV2ZWwsIG1ldGhvZF9ncm91cCwgeWVhciwgc2Nob29sX3llYXIpXQpjb3VudHNfc3RyYXRfaW5jcmVhc2VfbGV2ZWxbLCBpbmNyZWFzZSA6PSB0cmlhbHNfbG9ja2Rvd25bMl0vdHJpYWxzX2xvY2tkb3duWzFdLCBieSA9IC4oY291cnNlLCBsZXZlbCwgbWV0aG9kX2dyb3VwLCB5ZWFyKV0KY291bnRzX3N0cmF0X2luY3JlYXNlX2xldmVsWywgaW5jcmVhc2VfcGN0IDo9IHBhc3RlMCgiQ2hhbmdlOlxuIiwgc2NhbGVzOjpwZXJjZW50KGluY3JlYXNlLCBhY2N1cmFjeSA9IDIpKV0KYGBgCgoKIyMjIEZyZW5jaApgYGB7cn0KZ2dwbG90KGNvdW50c19zdHJhdF9ieV9kYXlbY291cnNlID09ICJGcmVuY2giXSwgCiAgICAgICBhZXMoZ3JvdXAgPSBzY2hvb2xfeWVhciwgY29sb3VyID0gc2Nob29sX3llYXIsIGZpbGwgPSBzY2hvb2xfeWVhcikpICsKICBmYWNldF9ncmlkKGJvb2tfdGl0bGVfc2ltcGxlIH4gbWV0aG9kX2dyb3VwKSArCiAgZ2VvbV9yZWN0KHhtaW4gPSBkYXRlX3NjaG9vbHNfY2xvc2VkLCB4bWF4ID0gZGF0ZV9zY2hvb2xzX29wZW5lZCwgeW1pbiA9IC0yZTUsIHltYXggPSAyLjJlNiwgZmlsbCA9ICJncmV5OTIiLCBjb2xvdXIgPSAiZ3JleTUwIiwgbHR5ID0gMikgKwogIGdlb21fcmliYm9uKGFlcyh4ID0gZG95X3Bvc2l4X2FsaWduZWQsIHltaW4gPSAwLCB5bWF4ID0gdHJpYWxzX3RvdGFsX3dlZWssICksIGFscGhhID0gLjIpICsKICBnZW9tX3RleHQoZGF0YSA9IGNvdW50c19zdHJhdF9pbmNyZWFzZVtjb3Vyc2UgPT0gIkZyZW5jaCIgJiBzY2hvb2xfeWVhciA9PSAiMTkvMjAiXSwgCiAgICAgICAgICAgIGFlcyhsYWJlbCA9IGluY3JlYXNlX3BjdCksCiAgICAgICAgICAgIHggPSBhcy5QT1NJWGN0KChhcy5udW1lcmljKGRhdGVfc2Nob29sc19jbG9zZWQpICsgYXMubnVtZXJpYyhkYXRlX3NjaG9vbHNfb3BlbmVkKSkvMiwgb3JpZ2luID0gIjE5NzAtMDEtMDEiKSwKICAgICAgICAgICAgeSA9IDMuNmU1LAogICAgICAgICAgICBjb2xvdXIgPSAiYmxhY2siLAogICAgICAgICAgICB2anVzdCA9IDEsCiAgICAgICAgICAgIHNob3cubGVnZW5kID0gRkFMU0UpICsKICBzY2FsZV94X2RhdGV0aW1lKGV4cGFuZCA9IGMoMCwgMCksIAogICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYXMuUE9TSVhjdChjKAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMS0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMS0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMy0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNS0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBhcy5QT1NJWGN0KGMoIjIwMTktMDktMDEgMDI6MDA6MDAgQ0VUIiwgIjIwMjAtMDctMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgZGF0ZV9sYWJlbHMgPSAiJWIiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGxpbWl0cyA9IGMoMCwgMy43NWU1KSwgbGFiZWxzID0gbnVtYmVyX2Zvcm1hdCkgKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGVuZCA9IC41LCBkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9ICJUcmlhbHMgcGVyIHdlZWsiLAogICAgICAgY29sb3VyID0gIlNjaG9vbCB5ZWFyIiwKICAgICAgIGZpbGwgPSAiU2Nob29sIHllYXIiLAogICAgICAgdGl0bGUgPSAiRnJlbmNoIikgKwogIHRoZW1lX3BhcGVyCgpnZ3NhdmUoIi4uL291dHB1dC90cmlhbF9oaXN0X2ZyZW5jaC5wZGYiLCB3aWR0aCA9IDE0LCBoZWlnaHQgPSAxMCkKZ2dzYXZlKCIuLi9vdXRwdXQvdHJpYWxfaGlzdF9mcmVuY2guZXBzIiwgd2lkdGggPSAxNCwgaGVpZ2h0ID0gMTApCmdnc2F2ZSgiLi4vb3V0cHV0L3RyaWFsX2hpc3RfZnJlbmNoLnBuZyIsIHdpZHRoID0gMTQsIGhlaWdodCA9IDEwKQpgYGAKClN0cmVhbWxpbmVkIHZlcnNpb24gZm9yIGluIHRoZSBwYXBlcjoKYGBge3J9CmdncGxvdChjb3VudHNfc3RyYXRfYnlfZGF5W2NvdXJzZSA9PSAiRnJlbmNoIl0sIAogICAgICAgYWVzKGdyb3VwID0gc2Nob29sX3llYXIsIGNvbG91ciA9IHNjaG9vbF95ZWFyLCBmaWxsID0gc2Nob29sX3llYXIpKSArCiAgZmFjZXRfZ3JpZChsZXZlbCB+IHllYXIpICsKICBnZW9tX3JlY3QoeG1pbiA9IGRhdGVfc2Nob29sc19jbG9zZWQsIHhtYXggPSBkYXRlX3NjaG9vbHNfb3BlbmVkLCB5bWluID0gLTJlNSwgeW1heCA9IDIuMmU2LCBmaWxsID0gImdyZXk5MiIsIGNvbG91ciA9ICJncmV5NTAiLCBsdHkgPSAyKSArCiAgZ2VvbV9yaWJib24oYWVzKHggPSBkb3lfcG9zaXhfYWxpZ25lZCwgeW1pbiA9IDAsIHltYXggPSB0cmlhbHNfdG90YWxfd2Vla19sZXZlbCwgKSwgYWxwaGEgPSAuMikgKwogIGdlb21fdGV4dChkYXRhID0gY291bnRzX3N0cmF0X2luY3JlYXNlX2xldmVsW2NvdXJzZSA9PSAiRnJlbmNoIiAmIHNjaG9vbF95ZWFyID09ICIxOS8yMCJdLCAKICAgICAgICAgICAgYWVzKGxhYmVsID0gaW5jcmVhc2VfcGN0KSwKICAgICAgICAgICAgeCA9IGFzLlBPU0lYY3QoKGFzLm51bWVyaWMoZGF0ZV9zY2hvb2xzX2Nsb3NlZCkgKyBhcy5udW1lcmljKGRhdGVfc2Nob29sc19vcGVuZWQpKS8yLCBvcmlnaW4gPSAiMTk3MC0wMS0wMSIpLAogICAgICAgICAgICB5ID0gMy42ZTUsCiAgICAgICAgICAgIGNvbG91ciA9ICJibGFjayIsCiAgICAgICAgICAgIHZqdXN0ID0gMSwKICAgICAgICAgICAgc2l6ZSA9IHJlbCgyLjc1KSwKICAgICAgICAgICAgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIHNjYWxlX3hfZGF0ZXRpbWUoZXhwYW5kID0gYygwLCAwKSwgCiAgICAgICAgICAgICAgICAgICBicmVha3MgPSBhcy5QT1NJWGN0KGMoCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEwLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA0LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA2LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGFzLlBPU0lYY3QoYygiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLCAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBkYXRlX2xhYmVscyA9ICIlYiIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgbGltaXRzID0gYygwLCAzLjc1ZTUpLCBsYWJlbHMgPSBudW1iZXJfZm9ybWF0KSArCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXNfZChlbmQgPSAuNSwgZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIlRyaWFscyBwZXIgd2VlayIsCiAgICAgICBjb2xvdXIgPSAiU2Nob29sIHllYXIiLAogICAgICAgZmlsbCA9ICJTY2hvb2wgeWVhciIpICsKICB0aGVtZV9wYXBlcgoKZ2dzYXZlKCIuLi9vdXRwdXQvdHJpYWxfaGlzdF9mcmVuY2hfbGV2ZWwucGRmIiwgd2lkdGggPSA5LCBoZWlnaHQgPSA1KQpnZ3NhdmUoIi4uL291dHB1dC90cmlhbF9oaXN0X2ZyZW5jaF9sZXZlbC5lcHMiLCB3aWR0aCA9IDksIGhlaWdodCA9IDUpCmdnc2F2ZSgiLi4vb3V0cHV0L3RyaWFsX2hpc3RfZnJlbmNoX2xldmVsLnBuZyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gNSkKYGBgCgpUaGVyZSBhcmUgdHdvIHJlYXNvbnMgd2h5IHRvdGFsIHRyaWFsIGNvdW50IG1heSBpbmNyZWFzZTogdGhlcmUgYXJlIG1vcmUgYWN0aXZlIHVzZXJzLCBvciBhY3RpdmUgdXNlcnMgY29tcGxldGUgbW9yZSB0cmlhbHMuCkdpdmVuIHRoZSBmaXhlZCBjdXJyaWN1bHVtLCB0aGUgZmlyc3QgcmVhc29uIHNlZW1zIG1vcmUgcGxhdXNpYmxlLgpXZSBjYW4gY29uZmlybSB0aGlzIGJ5IHBsb3R0aW5nIHRoZSB3ZWVrbHkgbnVtYmVyIG9mIHRyaWFscyBwZXIgdXNlci4KCmBgYHtyfQp1c2Vyc19zdHJhdCA8LSBjb3B5KGNvdW50c19zdHJhdCkKCnVzZXJzX3N0cmF0WywgYm9va190aXRsZV9zaW1wbGUgOj0gc3RyaW5ncjo6c3RyX3N1Yihib29rX3RpdGxlLCAzLCAtMTApXQp1c2Vyc19zdHJhdFssIGJvb2tfdGl0bGVfc2ltcGxlIDo9IGZhY3Rvcihib29rX3RpdGxlX3NpbXBsZSwgbGV2ZWxzID0gYygidm1ibyBiL2x3b28iLCAidm1ibyBiIiwgInZtYm8gYmsiLCAidm1ibyBrIiwgInZtYm8ga2d0IiwgInZtYm8tZ3QiLCAidm1ibyBndCIsICJ2bWJvLWd0L2hhdm8iLCAidm1ibyAodClodiIsICJoYXZvIiwgImhhdm8gdndvIiwgInZ3byIpKV0KCiMgU2ltcGxpZnkgdG8gdGhyZWUgbGV2ZWxzCnVzZXJzX3N0cmF0WywgbGV2ZWwgOj0gZHBseXI6OmNhc2Vfd2hlbigKICBncmVwbCggImh2IiwgYm9va190aXRsZSkgfiAiR2VuZXJhbCBzZWNvbmRhcnlcbihoYXZvKSIsCiAgZ3JlcGwoInZtYm8iLCBib29rX3RpdGxlKSB+ICJQcmUtdm9jYXRpb25hbFxuKHZtYm8pIiwKICBncmVwbCgiaGF2byIsIGJvb2tfdGl0bGUpIH4gIkdlbmVyYWwgc2Vjb25kYXJ5XG4oaGF2bykiLAogIGdyZXBsKCJ2d28iLCBib29rX3RpdGxlKSB+ICJQcmUtdW5pdmVyc2l0eVxuKHZ3bykiLAogIFRSVUUgfiAiT3RoZXIiKV0KdXNlcnNfc3RyYXRbLCBsZXZlbCA6PSBmYWN0b3IobGV2ZWwsIGxldmVscyA9IGMoIk90aGVyIiwgIlByZS12b2NhdGlvbmFsXG4odm1ibykiLCAiR2VuZXJhbCBzZWNvbmRhcnlcbihoYXZvKSIsICJQcmUtdW5pdmVyc2l0eVxuKHZ3bykiKSldCgp1c2Vyc19zdHJhdFssIHllYXIgOj0gZHBseXI6OmNhc2Vfd2hlbigKICBtZXRob2RfZ3JvdXAgPT0gIkxlZXJqYWFyIDEgKDVlIEVkLikiIH4gIlllYXIgMSIsCiAgbWV0aG9kX2dyb3VwID09ICJMZWVyamFhciAyICg1ZSBFZC4pIiB+ICJZZWFyIDIiLAogIG1ldGhvZF9ncm91cCA9PSAiTGVlcmphYXIgMyAoNWUgRWQuKSIgfiAiWWVhciAzIiwKICBtZXRob2RfZ3JvdXAgPT0gIkxlZXJqYWFyIDMvNCAoNWUgRWQuKSIgfiAiWWVhciAzLzQiLAogIG1ldGhvZF9ncm91cCA9PSAiTGVlcmphYXIgNCAoNWUgRWQuKSIgfiAiWWVhciA0IiwKICBtZXRob2RfZ3JvdXAgPT0gIlR3ZWVkZSBGYXNlICg2ZSBFZC4pIiB+ICJUd2VlZGUgRmFzZSIsCiAgVFJVRSB+ICJPdGhlciIpXQoKdXNlcnNfc3RyYXRbc2Nob29sX3llYXIgPT0gIjE4LzE5IiwgZG95X3Bvc2l4X2FsaWduZWQgOj0gYXMuUE9TSVhjdChkb3lfcG9zaXggKyAzNjUqMjQqNjAqNjAsIG9yaWdpbiA9ICIxOTcwLTAxLTAxIildCnVzZXJzX3N0cmF0W3NjaG9vbF95ZWFyID09ICIxOS8yMCIsIGRveV9wb3NpeF9hbGlnbmVkIDo9IGRveV9wb3NpeF0KdXNlcnNfc3RyYXRbLCBkb3lfcG9zaXhfYWxpZ25lZF93ZWVrIDo9IGN1dC5QT1NJWHQoZG95X3Bvc2l4X2FsaWduZWQsICJ3ZWVrIildCnVzZXJzX3N0cmF0X2J5X3dlZWsgPC0gdXNlcnNfc3RyYXRbLCAuKHVzZXJzX3dlZWtfbGV2ZWwgPSAuTiksIGJ5ID0gLihzY2hvb2xfeWVhciwgY291cnNlLCB5ZWFyLCBsZXZlbCwgZG95X3Bvc2l4X2FsaWduZWRfd2VlayldCmBgYAoKYGBge3J9CmNvdW50c19zdHJhdF9ieV9kYXkgPC0gY291bnRzX3N0cmF0X2J5X2RheVt1c2Vyc19zdHJhdF9ieV93ZWVrLCBvbiA9IGMoInNjaG9vbF95ZWFyIiwgImNvdXJzZSIsICJ5ZWFyIiwgImxldmVsIiwgImRveV9wb3NpeF9hbGlnbmVkX3dlZWsiKV1bLCB0cmlhbHNfcGVyX3VzZXJfd2VlayA6PSB0cmlhbHNfdG90YWxfd2Vla19sZXZlbCAvIHVzZXJzX3dlZWtfbGV2ZWxdCmBgYAoKYGBge3J9CmdncGxvdChjb3VudHNfc3RyYXRfYnlfZGF5W2NvdXJzZSA9PSAiRnJlbmNoIl0sIAogICAgICAgYWVzKGdyb3VwID0gc2Nob29sX3llYXIsIGNvbG91ciA9IHNjaG9vbF95ZWFyLCBmaWxsID0gc2Nob29sX3llYXIpKSArCiAgZmFjZXRfZ3JpZChsZXZlbCB+IHllYXIpICsKICBnZW9tX3JlY3QoeG1pbiA9IGRhdGVfc2Nob29sc19jbG9zZWQsIHhtYXggPSBkYXRlX3NjaG9vbHNfb3BlbmVkLCB5bWluID0gLTJlNSwgeW1heCA9IDIuMmU2LCBmaWxsID0gImdyZXk5MiIsIGNvbG91ciA9ICJncmV5NTAiLCBsdHkgPSAyKSArCiAgZ2VvbV9yaWJib24oYWVzKHggPSBkb3lfcG9zaXhfYWxpZ25lZCwgeW1pbiA9IDAsIHltYXggPSB0cmlhbHNfcGVyX3VzZXJfd2VlaywgKSwgYWxwaGEgPSAuMikgKwogIHNjYWxlX3hfZGF0ZXRpbWUoZXhwYW5kID0gYygwLCAwKSwgCiAgICAgICAgICAgICAgICAgICBicmVha3MgPSBhcy5QT1NJWGN0KGMoCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEwLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA0LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA2LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGFzLlBPU0lYY3QoYygiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLCAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBkYXRlX2xhYmVscyA9ICIlYiIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgbGFiZWxzID0gbnVtYmVyX2Zvcm1hdCkgKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGVuZCA9IC41LCBkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9ICJUcmlhbHMgcGVyIHVzZXIgcGVyIHdlZWsiLAogICAgICAgY29sb3VyID0gIlNjaG9vbCB5ZWFyIiwKICAgICAgIGZpbGwgPSAiU2Nob29sIHllYXIiKSArCiAgdGhlbWVfcGFwZXIKCmBgYAoKQXMgZXhwZWN0ZWQsIHRoZXJlIGlzIGVzc2VudGlhbGx5IG5vIGNoYW5nZSBpbiB0aGUgbnVtYmVyIG9mIHRyaWFscyBlYWNoIHVzZXIgY29tcGxldGVzLCBzbyB0aGUgaW5jcmVhc2VkIHRyaWFsIGNvdW50IHRoYXQgd2Ugc2VlIGNvbWVzIGZyb20gbW9yZSB1c2VycyBiZWluZyBhY3RpdmUuCgoKCiMjIyBFbmdsaXNoCgpgYGB7cn0KZ2dwbG90KGNvdW50c19zdHJhdF9ieV9kYXlbY291cnNlID09ICJFbmdsaXNoIl0sIAogICAgICAgYWVzKGdyb3VwID0gc2Nob29sX3llYXIsIGNvbG91ciA9IHNjaG9vbF95ZWFyLCBmaWxsID0gc2Nob29sX3llYXIpKSArCiAgZmFjZXRfZ3JpZChib29rX3RpdGxlX3NpbXBsZSB+IG1ldGhvZF9ncm91cCkgKwogIGdlb21fcmVjdCh4bWluID0gZGF0ZV9zY2hvb2xzX2Nsb3NlZCwgeG1heCA9IGRhdGVfc2Nob29sc19vcGVuZWQsIHltaW4gPSAtMmU1LCB5bWF4ID0gMi4yZTYsIGZpbGwgPSAiZ3JleTkyIiwgY29sb3VyID0gImdyZXk1MCIsIGx0eSA9IDIpICsKICBnZW9tX3JpYmJvbihhZXMoeCA9IGRveV9wb3NpeF9hbGlnbmVkLCB5bWluID0gMCwgeW1heCA9IHRyaWFsc190b3RhbF93ZWVrLCApLCBhbHBoYSA9IC4yKSArCiAgZ2VvbV90ZXh0KGRhdGEgPSBjb3VudHNfc3RyYXRfaW5jcmVhc2VbY291cnNlID09ICJFbmdsaXNoIiAmIHNjaG9vbF95ZWFyID09ICIxOS8yMCJdLCAKICAgICAgICAgICAgYWVzKGxhYmVsID0gaW5jcmVhc2VfcGN0KSwKICAgICAgICAgICAgeCA9IGFzLlBPU0lYY3QoKGFzLm51bWVyaWMoZGF0ZV9zY2hvb2xzX2Nsb3NlZCkgKyBhcy5udW1lcmljKGRhdGVfc2Nob29sc19vcGVuZWQpKS8yLCBvcmlnaW4gPSAiMTk3MC0wMS0wMSIpLAogICAgICAgICAgICB5ID0gMy42ZTUsCiAgICAgICAgICAgIGNvbG91ciA9ICJibGFjayIsCiAgICAgICAgICAgIHZqdXN0ID0gMSwKICAgICAgICAgICAgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIHNjYWxlX3hfZGF0ZXRpbWUoZXhwYW5kID0gYygwLCAwKSwgCiAgICAgICAgICAgICAgICAgICBicmVha3MgPSBhcy5QT1NJWGN0KGMoCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTA5LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTExLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAxLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAzLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA1LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA3LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGFzLlBPU0lYY3QoYygiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLCAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBkYXRlX2xhYmVscyA9ICIlYiIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgbGltaXRzID0gYygwLCAzLjc1ZTUpLCBsYWJlbHMgPSBudW1iZXJfZm9ybWF0KSArCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXNfZChlbmQgPSAuNSwgZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIlRyaWFscyBwZXIgd2VlayIsCiAgICAgICBjb2xvdXIgPSAiU2Nob29sIHllYXIiLAogICAgICAgZmlsbCA9ICJTY2hvb2wgeWVhciIsCiAgICAgICB0aXRsZSA9ICJFbmdsaXNoIikgKwogIHRoZW1lX3BhcGVyCgpnZ3NhdmUoIi4uL291dHB1dC90cmlhbF9oaXN0X2VuZ2xpc2gucGRmIiwgd2lkdGggPSAxNCwgaGVpZ2h0ID0gMTApCmdnc2F2ZSgiLi4vb3V0cHV0L3RyaWFsX2hpc3RfZW5nbGlzaC5lcHMiLCB3aWR0aCA9IDE0LCBoZWlnaHQgPSAxMCkKZ2dzYXZlKCIuLi9vdXRwdXQvdHJpYWxfaGlzdF9lbmdsaXNoLnBuZyIsIHdpZHRoID0gMTQsIGhlaWdodCA9IDEwKQpgYGAKClN0cmVhbWxpbmVkIHZlcnNpb24gZm9yIGluIHRoZSBwYXBlcjoKYGBge3J9CmdncGxvdChjb3VudHNfc3RyYXRfYnlfZGF5W2NvdXJzZSA9PSAiRW5nbGlzaCIgJiBsZXZlbCAhPSAiT3RoZXIiXSwgCiAgICAgICBhZXMoZ3JvdXAgPSBzY2hvb2xfeWVhciwgY29sb3VyID0gc2Nob29sX3llYXIsIGZpbGwgPSBzY2hvb2xfeWVhcikpICsKICBmYWNldF9ncmlkKGxldmVsIH4geWVhcikgKwogIGdlb21fcmVjdCh4bWluID0gZGF0ZV9zY2hvb2xzX2Nsb3NlZCwgeG1heCA9IGRhdGVfc2Nob29sc19vcGVuZWQsIHltaW4gPSAtMmU1LCB5bWF4ID0gMi4yZTYsIGZpbGwgPSAiZ3JleTkyIiwgY29sb3VyID0gImdyZXk1MCIsIGx0eSA9IDIpICsKICBnZW9tX3JpYmJvbihhZXMoeCA9IGRveV9wb3NpeF9hbGlnbmVkLCB5bWluID0gMCwgeW1heCA9IHRyaWFsc190b3RhbF93ZWVrX2xldmVsLCApLCBhbHBoYSA9IC4yKSArCiAgZ2VvbV90ZXh0KGRhdGEgPSBjb3VudHNfc3RyYXRfaW5jcmVhc2VfbGV2ZWxbY291cnNlID09ICJFbmdsaXNoIiAmIGxldmVsICE9ICJPdGhlciIgJiBzY2hvb2xfeWVhciA9PSAiMTkvMjAiXSwgCiAgICAgICAgICAgIGFlcyhsYWJlbCA9IGluY3JlYXNlX3BjdCksCiAgICAgICAgICAgIHggPSBhcy5QT1NJWGN0KChhcy5udW1lcmljKGRhdGVfc2Nob29sc19jbG9zZWQpICsgYXMubnVtZXJpYyhkYXRlX3NjaG9vbHNfb3BlbmVkKSkvMiwgb3JpZ2luID0gIjE5NzAtMDEtMDEiKSwKICAgICAgICAgICAgeSA9IDkuNmU1LAogICAgICAgICAgICBjb2xvdXIgPSAiYmxhY2siLAogICAgICAgICAgICB2anVzdCA9IDEsCiAgICAgICAgICAgIHNpemUgPSByZWwoMi43NSksCiAgICAgICAgICAgIHNob3cubGVnZW5kID0gRkFMU0UpICsKICBzY2FsZV94X2RhdGV0aW1lKGV4cGFuZCA9IGMoMCwgMCksIAogICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYXMuUE9TSVhjdChjKAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNi0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBhcy5QT1NJWGN0KGMoIjIwMTktMDktMDEgMDI6MDA6MDAgQ0VUIiwgIjIwMjAtMDctMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgZGF0ZV9sYWJlbHMgPSAiJWIiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGxpbWl0cyA9IGMoMCwgMWU2KSwgbGFiZWxzID0gbnVtYmVyX2Zvcm1hdCkgKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGVuZCA9IC41LCBkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9ICJUcmlhbHMgcGVyIHdlZWsiLAogICAgICAgY29sb3VyID0gIlNjaG9vbCB5ZWFyIiwKICAgICAgIGZpbGwgPSAiU2Nob29sIHllYXIiKSArCiAgdGhlbWVfcGFwZXIKCmdnc2F2ZSgiLi4vb3V0cHV0L3RyaWFsX2hpc3RfZW5nbGlzaF9sZXZlbC5wZGYiLCB3aWR0aCA9IDksIGhlaWdodCA9IDUpCmdnc2F2ZSgiLi4vb3V0cHV0L3RyaWFsX2hpc3RfZW5nbGlzaF9sZXZlbC5lcHMiLCB3aWR0aCA9IDksIGhlaWdodCA9IDUpCmdnc2F2ZSgiLi4vb3V0cHV0L3RyaWFsX2hpc3RfZW5nbGlzaF9sZXZlbC5wbmciLCB3aWR0aCA9IDksIGhlaWdodCA9IDUpCmBgYAoKQWxzbyBwbG90IHRoZSB3ZWVrbHkgbnVtYmVyIG9mIHRyaWFscyBwZXIgdXNlci4KQXMgd2l0aCBGcmVuY2gsIHRoaXMgbnVtYmVyIHN0YXlzIG1vcmUgb3IgbGVzcyBjb25zdGFudDoKYGBge3J9CmdncGxvdChjb3VudHNfc3RyYXRfYnlfZGF5W2NvdXJzZSA9PSAiRW5nbGlzaCJdLCAKICAgICAgIGFlcyhncm91cCA9IHNjaG9vbF95ZWFyLCBjb2xvdXIgPSBzY2hvb2xfeWVhciwgZmlsbCA9IHNjaG9vbF95ZWFyKSkgKwogIGZhY2V0X2dyaWQobGV2ZWwgfiB5ZWFyKSArCiAgZ2VvbV9yZWN0KHhtaW4gPSBkYXRlX3NjaG9vbHNfY2xvc2VkLCB4bWF4ID0gZGF0ZV9zY2hvb2xzX29wZW5lZCwgeW1pbiA9IC0yZTUsIHltYXggPSAyLjJlNiwgZmlsbCA9ICJncmV5OTIiLCBjb2xvdXIgPSAiZ3JleTUwIiwgbHR5ID0gMikgKwogIGdlb21fcmliYm9uKGFlcyh4ID0gZG95X3Bvc2l4X2FsaWduZWQsIHltaW4gPSAwLCB5bWF4ID0gdHJpYWxzX3Blcl91c2VyX3dlZWssICksIGFscGhhID0gLjIpICsKICBzY2FsZV94X2RhdGV0aW1lKGV4cGFuZCA9IGMoMCwgMCksIAogICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYXMuUE9TSVhjdChjKAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNi0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBhcy5QT1NJWGN0KGMoIjIwMTktMDktMDEgMDI6MDA6MDAgQ0VUIiwgIjIwMjAtMDctMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgZGF0ZV9sYWJlbHMgPSAiJWIiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGxhYmVscyA9IG51bWJlcl9mb3JtYXQpICsKICBzY2FsZV9jb2xvdXJfdmlyaWRpc19kKGVuZCA9IC41LCBkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChlbmQgPSAuNSwgZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiVHJpYWxzIHBlciB1c2VyIHBlciB3ZWVrIiwKICAgICAgIGNvbG91ciA9ICJTY2hvb2wgeWVhciIsCiAgICAgICBmaWxsID0gIlNjaG9vbCB5ZWFyIikgKwogIHRoZW1lX3BhcGVyCgpgYGAKCgojIyBRdWVzdGlvbiB0eXBlCgpUaGVyZSBhcmUgZGlmZmVyZW50IHF1ZXN0aW9uIGZvcm1hdHM6IG9wZW4tYW5zd2VyLCBpbiB3aGljaCB0aGUgc3R1ZGVudCB0eXBlcyB0aGUgYW5zd2VyLCBhbmQgbXVsdGlwbGUtY2hvaWNlLCBpbiB3aGljaCB0aGUgc3R1ZGVudCBzZWxlY3RzIHRoZSBhbnN3ZXIgZnJvbSBhIHNldCBvZiAzIG9yIDQgb3B0aW9ucy4KCmBgYHtyfQpkYiA8LSBkYl9jb25uZWN0KCkKcXVlc3Rpb25fdHlwZSA8LSBkYkdldFF1ZXJ5KGRiLCAKICAgICAgICAgICAgICAgICAgICAgICJTRUxFQ1Qgci5tZXRob2QgQVMgJ21ldGhvZCcsCiAgICAgICAgICAgICAgICAgICAgICBEQVRFKHIuZGF0ZSArIDM2MDAsICd1bml4ZXBvY2gnKSBBUyAnZG95JywKICAgICAgICAgICAgICAgICAgICAgIHIuY2hvaWNlcyBBUyAnY2hvaWNlcycsCiAgICAgICAgICAgICAgICAgICAgICBDT1VOVCgqKSBBUyAnbicKICAgICAgICAgICAgICAgICAgICAgIEZST00gJ3Jlc3BvbnNlc19ub2R1cGxpY2F0ZXMnIHIKICAgICAgICAgICAgICAgICAgICAgIFdIRVJFIHIuc3R1ZHkgPT0gMAogICAgICAgICAgICAgICAgICAgICAgR1JPVVAgQlkgci5tZXRob2QsCiAgICAgICAgICAgICAgICAgICAgICBEQVRFKHIuZGF0ZSArIDM2MDAsICd1bml4ZXBvY2gnKSwKICAgICAgICAgICAgICAgICAgICAgIHIuY2hvaWNlcyIKKQpzZXREVChxdWVzdGlvbl90eXBlKQpkYl9kaXNjb25uZWN0KGRiKQpgYGAKCkFkZCBhIHNjaG9vbCB5ZWFyIGNvbHVtbiAoY3V0b2ZmIGRhdGU6IDEgQXVndXN0KToKYGBge3J9CnF1ZXN0aW9uX3R5cGVbLCBkb3lfcG9zaXggOj0gYXMuUE9TSVhjdChkb3kpXQpxdWVzdGlvbl90eXBlWywgc2Nob29sX3llYXIgOj0gaWZlbHNlKGRveV9wb3NpeCA8ICIyMDE5LTA4LTAxIiwgIjE4LzE5IiwgIjE5LzIwIildCmBgYAoKQWRkIHNlbnNpYmxlIGNvdXJzZSBuYW1lczoKYGBge3J9CnF1ZXN0aW9uX3R5cGVbLCBjb3Vyc2UgOj0gaWZlbHNlKG1ldGhvZCA9PSAiR3JhbmRlcyBMaWduZXMiLCAiRnJlbmNoIiwgaWZlbHNlKG1ldGhvZCA9PSAiU3RlcHBpbmcgU3RvbmVzIiwgIkVuZ2xpc2giLCAiR2VybWFuIikpXQpgYGAKCkFsaWduIHNjaG9vbCB5ZWFyczoKYGBge3J9CnF1ZXN0aW9uX3R5cGVbc2Nob29sX3llYXIgPT0gIjE4LzE5IiwgZG95X3Bvc2l4X2FsaWduZWQgOj0gYXMuUE9TSVhjdChkb3lfcG9zaXggKyAzNjUqMjQqNjAqNjAsIG9yaWdpbiA9ICIxOTcwLTAxLTAxIildCnF1ZXN0aW9uX3R5cGVbc2Nob29sX3llYXIgPT0gIjE5LzIwIiwgZG95X3Bvc2l4X2FsaWduZWQgOj0gZG95X3Bvc2l4XQpgYGAKClVzZSBjdXQuRGF0ZSgpIHRvIGJpbiBkYXRlcyBieSB3ZWVrLiBFYWNoIGRheSBpcyBhc3NpZ25lZCB0aGUgZGF0ZSBvZiB0aGUgbW9zdCByZWNlbnQgTW9uZGF5LgpgYGB7cn0KcXVlc3Rpb25fdHlwZVssIGRveV9wb3NpeF93ZWVrIDo9IGN1dC5QT1NJWHQoZG95X3Bvc2l4LCAid2VlayIpXQpxdWVzdGlvbl90eXBlWywgZG95X3Bvc2l4X2FsaWduZWRfd2VlayA6PSBjdXQuUE9TSVh0KGRveV9wb3NpeF9hbGlnbmVkLCAid2VlayIpXQpgYGAKCmBgYHtyfQpxdWVzdGlvbl90eXBlX2J5X3dlZWsgPC0gcXVlc3Rpb25fdHlwZVssIC4obiA9IHN1bShuKSksIGJ5ID0gLihjb3Vyc2UsIHNjaG9vbF95ZWFyLCBkb3lfcG9zaXhfYWxpZ25lZF93ZWVrLCBjaG9pY2VzKV0KYGBgCgpgYGB7cn0KZ2dwbG90KHF1ZXN0aW9uX3R5cGVfYnlfd2Vla1tjb3Vyc2UgJWluJSBjKCJFbmdsaXNoIiwgIkZyZW5jaCIpXSwgYWVzKHggPSBhcy5QT1NJWGN0KGRveV9wb3NpeF9hbGlnbmVkX3dlZWspLCB5ID0gbiwgZ3JvdXAgPSBpbnRlcmFjdGlvbihzY2hvb2xfeWVhcixhcy5mYWN0b3IoY2hvaWNlcykpLCBjb2xvdXIgPSBzY2hvb2xfeWVhcikpICsKICBmYWNldF9ncmlkKGNvdXJzZSB+IGNob2ljZXMpICsKICBnZW9tX2xpbmUoKSArCiAgc2NhbGVfeF9kYXRldGltZShleHBhbmQgPSBjKDAsIDApLAogICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYXMuUE9TSVhjdChjKAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNi0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBhcy5QT1NJWGN0KGMoIjIwMTktMDktMDEgMDI6MDA6MDAgQ0VUIiwgIjIwMjAtMDctMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgZGF0ZV9sYWJlbHMgPSAiJWIiKSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiVHJpYWxzIiwKICAgICAgIGNvbG91ciA9ICJTY2hvb2wgeWVhciIpICsKICB0aGVtZV9wYXBlcgoKYGBgCgpgYGB7cn0KcXVlc3Rpb25fdHlwZVssIC4obiA9IHN1bShuKSksIGJ5ID0gLihjb3Vyc2UsIG1jcSA9IGNob2ljZXM+MSwgc2Nob29sX3llYXIpXVssIC4ocGVyY19tY3EgPSBuW21jcSA9PSBUUlVFXS9zdW0obikpLCBieSA9IC4oY291cnNlLCBzY2hvb2xfeWVhcildCmBgYAoKVGhlcmUgaXMgYSBjbGVhciBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIGxhbmd1YWdlcyBpbiB0aGUgcXVlc3Rpb24gZm9ybWF0IHVzZWQ6IEVuZ2xpc2ggdXNlcyBhbG1vc3QgZXhjbHVzaXZlbHkgNC1hbHRlcm5hdGl2ZSBNQ1FzLCB3aGlsZSBGcmVuY2ggdXNlcyBhIG1peCBvZiBNQ1FzIChpbmNsdWRpbmcgYSBzbWFsbCBudW1iZXIgb2YgMy1hbHRlcm5hdGl2ZSBxdWVzdGlvbnMpIGFuZCBvcGVuLWFuc3dlciBxdWVzdGlvbnMuCgoKIyBTZXNzaW9uIGluZm8KYGBge3J9CnNlc3Npb25JbmZvKCkKYGBgCgo=